从 @types/react 源码中挖掘一些 Typescript 使用技巧吧。前端
泛型能够指代可能的参数类型,但指代任意类型范围太模糊,当咱们须要对参数类型加以限制,或者肯定只处理某种类型参数时,就能够对泛型进行 extends 修饰。react
问题:React.lazy
须要限制返回值是一个 Promise<T>
类型,且 T
必须是 React 组件类型。git
方案:github
function lazy<T extends ComponentType<any>>(
factory: () => Promise<{ default: T }>
): LazyExoticComponent<T>;
复制代码
T extends ComponentType
确保了 T 这个类型必定符合 ComponentType
这个 React 组件类型定义,咱们再将 T 用到 Promise<{ default: T }>
位置便可。typescript
若是有一种场景,须要拿到一个类型,这个类型是当某个参数符合某种结构时,这个结构内的一种子类型,就须要结合 泛型 extends + infer 了。编程
问题:React.useReducer
第一个参数是 Reducer,第二个参数是初始化参数,其实第二个参数的类型是第一个参数中回调函数第一个参数的类型,那咱们怎么将这两个参数的关系联系到一块儿呢?微信
方案:函数
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
中将第一个回调参数挖了出来并返回。ui
ReducerState
定义中 R extends Reducer<infer S, any> ? S : never
的含义是:若是 R 符合 Reducer<infer S, any>
类型,则返回类型 S
,这个 S
是 Reducer<infer S>
也就是 State 位置的类型,不然返回 never
类型。spa
因此 infer 表示待推断类型,是很是强大的功能,能够指定在任意位置代指其类型,并配合 extends 判断是否符合结构,可使类型推断具有必定编程能力。
要用 extends 的另外一个缘由是,只有 extends 才能将结构描述出来,咱们才能精肯定义 infer 指代类型的位置。
当一个类型拥有多种使用可能性时,能够采用类型重载定义复数类型,Typescript 做用时会逐个匹配并找到第一个知足条件的。
问题:createElement
第一个参数支持 FunctionComponent 与 ClassComponent,并且传入参数不一样,返回值的类型也不一样。
方案:
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 元素,咱们但愿这个函数具有类型收窄的功能。
方案:
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
关键字时其被调用时具有类型收窄的功能。
通常定义函数类型咱们用 type
,但有些状况下定义的函数既可被调用,也有一些默认属性值须要定义,咱们能够继续用 Interface 定义。
问题:FunctionComponent
既能够看成函数调用,同时又能定义 defaultProps
displayName
等固定属性。
方案:
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
表示这种类型的变量能够做为函数执行:
const App: FunctionComponent = () => <div />;
App.displayName = "App";
复制代码
看完文章内容,相信你已经能够独立读懂 @types/react 这个包的全部类型定义!
更多基础内容能够阅读 精读《Typescript2.0 - 2.9》 与 精读《Typescript 3.2 新特性》,因为 TS 更新频繁,后续 TS 技巧可能继续以阅读源码方式进行,但愿此次选用的 React 类型源码可让你印象深入。
讨论地址是:精读《@types/react 值得注意的 TS 技巧》 · Issue #245 · dt-fe/weekly
若是你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。
关注 前端精读微信公众号
版权声明:自由转载-非商用-非衍生-保持署名(创意共享 3.0 许可证)