## 1 引言

从 [@types/react](https://unpkg.com/browse/@types/react@16.9.34/index.d.ts) 源码中挖掘一些 Typescript 使用技巧吧。

## 2 精读

### 泛型 extends

泛型可以指代可能的参数类型，但指代任意类型范围太模糊，当我们需要对参数类型加以限制，或者确定只处理某种类型参数时，就可以对泛型进行 extends 修饰。

问题：`React.lazy` 需要限制返回值是一个 `Promise<T>` 类型，且 `T` 必须是 React 组件类型。

方案：

```typescript
function lazy<T extends ComponentType<any>>(
  factory: () => Promise<{ default: T }>
): LazyExoticComponent<T>;
```

`T extends ComponentType` 确保了 T 这个类型一定符合 `ComponentType` 这个 React 组件类型定义，我们再将 T 用到 `Promise<{ default: T }>` 位置即可。

## 泛型 extends + infer

如果有一种场景，需要拿到一个类型，这个类型是当某个参数符合某种结构时，这个结构内的一种子类型，就需要结合 泛型 extends + infer 了。

问题：`React.useReducer` 第一个参数是 Reducer，第二个参数是初始化参数，其实第二个参数的类型是第一个参数中回调函数第一个参数的类型，那我们怎么将这两个参数的关系联系到一起呢？

方案：

```typescript
function useReducer<R extends Reducer<any, any>, I>(
  reducer: R,
  initializerArg: I & ReducerState<R>,
  initializer: (arg: I & ReducerState<R>) => ReducerState<R>
): [ReducerState<R>, Dispatch<ReducerAction<R>>];

type ReducerState<R extends Reducer<any, any>> = R extends Reducer<infer S, any>
  ? S
  : never;
```

`R extends Reducer<any, any>` 的意思在上面已经提过了，也就是 R 必须符合 `Reducer` 结构，也就是 `reducer` 必须符合这个结构，之后重点来了：`initializerArg` 利用 `ReducerState` 这个类型直接从 `reducer` 的类型 `R` 中将第一个回调参数挖了出来并返回。

`ReducerState` 定义中 `R extends Reducer<infer S, any> ? S : never` 的含义是：如果 R 符合 `Reducer<infer S, any>` 类型，则返回类型 `S`，这个 `S` 是 `Reducer<infer S>` 也就是 State 位置的类型，否则返回 `never` 类型。

所以 infer 表示待推断类型，是非常强大的功能，可以指定在任意位置代指其类型，并配合 extends 判断是否符合结构，可以使类型推断具备一定编程能力。

要用 extends 的另一个原因是，只有 extends 才能将结构描述出来，我们才能精确定义 infer 指代类型的位置。

### 类型重载

当一个类型拥有多种使用可能性时，可以采用类型重载定义复数类型，Typescript 作用时会逐个匹配并找到第一个满足条件的。

问题：`createElement` 第一个参数支持 FunctionComponent 与 ClassComponent，而且传入参数不同，返回值的类型也不同。

方案：

```typescript
function createElement<P extends {}>(
  type: FunctionComponent<P>,
  props?: (Attributes & P) | null,
  ...children: ReactNode[]
): FunctionComponentElement<P>;
function createElement<P extends {}>(
  type: ClassType<
    P,
    ClassicComponent<P, ComponentState>,
    ClassicComponentClass<P>
  >,
  props?: (ClassAttributes<ClassicComponent<P, ComponentState>> & P) | null,
  ...children: ReactNode[]
): CElement<P, ClassicComponent<P, ComponentState>>;
```

将 `createElement` 写两遍及以上，并配合不同的参数类型与返回值类型即可。

### 自定义类型收窄

我们可以通过 `typeof` 或 `instanceof` 做一些类型收窄工作，但有些类型甚至自定义类型的收窄判断函数需要自定义，我们可以通过 `is` 关键字定义自定义类型收窄判断函数。

问题：`isValidElement` 判断对象是否是合法的 React 元素，我们希望这个函数具备类型收窄的功能。

方案：

```typescript
function isValidElement<P>(
  object: {} | null | undefined
): object is ReactElement<P>;

const element: string | ReactElement = "";

if (isValidElement(element)) {
  element; // 自动推导类型为 ReactElement
} else {
  element; // 自动推导类型为 string
}
```

基于这个方案，我们可以创建一些很有用的函数，比如 `isArray`，`isMap`，`isSet` 等等，通过 `is` 关键字时其被调用时具备类型收窄的功能。

### 用 Interface 定义函数

一般定义函数类型我们用 `type`，但有些情况下定义的函数既可被调用，也有一些默认属性值需要定义，我们可以继续用 Interface 定义。

问题：`FunctionComponent` 既可以当作函数调用，同时又能定义 `defaultProps` `displayName` 等固定属性。

方案：

```typescript
interface FunctionComponent<P = {}> {
  (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
  propTypes?: WeakValidationMap<P>;
  contextTypes?: ValidationMap<any>;
  defaultProps?: Partial<P>;
  displayName?: string;
}
```

`(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null` 表示这种类型的变量可以作为函数执行：

```jsx
const App: FunctionComponent = () => <div />;
App.displayName = "App";
```

## 3 总结

看完文章内容，相信你已经可以独立读懂 [@types/react](https://unpkg.com/browse/@types/react@16.9.34/index.d.ts) 这个包的所有类型定义！ 

更多基础内容可以阅读 [精读《Typescript2.0 - 2.9》](https://github.com/dt-fe/weekly/blob/7de3c77c3bdd7304c9e4b0c0f70c3ba6968ebd29/058.%E7%B2%BE%E8%AF%BB%E3%80%8ATypescript2.0%20-%202.9%E3%80%8B.md) 与 [精读《Typescript 3.2 新特性》](https://github.com/dt-fe/weekly/blob/v2/084.%E7%B2%BE%E8%AF%BB%E3%80%8ATypescript%203.2%20%E6%96%B0%E7%89%B9%E6%80%A7%E3%80%8B.md)，由于 TS 更新频繁，后续 TS 技巧可能继续以阅读源码方式进行，希望这次选用的 React 类型源码可以让你印象深刻。

> 讨论地址是：[精读《@types/react 值得注意的 TS 技巧》 · Issue #245 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/245)

**如果你想参与讨论，请 [点击这里](https://github.com/dt-fe/weekly)，每周都有新的主题，周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**

> 关注 **前端精读微信公众号**

<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">

> 版权声明：自由转载-非商用-非衍生-保持署名（[创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)）
