理解TypeScript中的infer关键字

前言

infer是在typescript 2.8中新增的关键字,距离如今3.9.3已经有两年出头了,趁着今天恰好使用了infer,因此好好整理一番javascript

infer

infer能够在extends的条件语句中推断待推断的类型html

例如在文档的示例中,使用infer来推断函数的返回值类型java

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

type func = () => number;
type variable = string;
type funcReturnType = ReturnType<func>; // funcReturnType 类型为 number
type varReturnType = ReturnType<variable>; // varReturnType 类型为 string
复制代码

在这个例子中,infer R表明待推断的返回值类型,若是T是一个函数,则返回函数的返回值,不然返回anygit

仅仅经过这一个例子,是很难看出infer是用来干什么的,还须要多看几个例子github

infer解包

infer的做用不止是推断返回值,还能够解包,我以为这是比较经常使用的typescript

假如想在获取数组里的元素类型,在不会infer以前我是这样作的数组

type Ids = number[];
type Names = string[];

type Unpacked<T> = T extends Names ? string : T extends Ids ? number : T;

type idType = Unpacked<Ids>; // idType 类型为 number
type nameType = Unpacked<Names>; // nameType 类型为string
复制代码

上次我写了20多行,就为了获取一堆各类不一样类型的数组里的元素类型,然而若是使用infer来解包,会变得十分简单函数

type Unpacked<T> = T extends (infer R)[] ? R : T;

type idType = Unpacked<Ids>; // idType 类型为 number
type nameType = Unpacked<Names>; // nameType 类型为string
复制代码

这里T extends (infer R)[] ? R : T的意思是,若是T是某个待推断类型的数组,则返回推断的类型,不然返回Tpost

再好比,想要获取一个Promise<xxx>类型中的xxx类型,在不使用infer的状况下我想不到何解学习

type Response = Promise<number[]>;
type Unpacked<T> = T extends Promise<infer R> ? R : T;

type resType = Unpacked<Response>; // resType 类型为number[]
复制代码

infer推断联合类型

仍是官方文档的例子

type Foo<T> = T extends { a: infer U; b: infer U } ? U : never;

type T10 = Foo<{ a: string; b: string }>; // T10类型为 string
type T11 = Foo<{ a: string; b: number }>; // T11类型为 string | number
复制代码

同一个类型变量在推断的值有多种状况的时候会推断为联合类型,针对这个特性,很方便的能够将元组转为联合类型

type ElementOf<T> = T extends (infer R)[] ? R : never;

type TTuple = [string, number];
type Union = ElementOf<TTuple>; // Union 类型为 string | number
复制代码

React中infer的使用

Reacttypescript源码中应该经常使用infer

就拿useReducer来举例子,若是咱们这样使用useReducer

const reducer = (x: number) => x + 1;
const [state, dispatch] = useReducer(reducer, '');
// Argument of type "" is not assignable to parameter of type 'number'.
复制代码

这里useReducer会报一个类型错误,说""不能赋值给number类型

那么React这里是如何经过reducer函数的类型来判断state的类型呢?

查看userReducer的定义,定义以下

function useReducer<R extends Reducer<any, any>, I>(
  reducer: R,
  // ReducerState 推断类型
  initializerArg: I & ReducerState<R>,
  initializer: (arg: I & ReducerState<R>) => ReducerState<R>
): [ReducerState<R>, Dispatch<ReducerAction<R>>];

// infer推断
type ReducerState<R extends Reducer<any, any>> = R extends Reducer<infer S, any>
  ? S
  : never;
// Reducer类型
type Reducer<S, A> = (prevState: S, action: A) => S;
复制代码

一切明了了,使用了infer推断reducer函数里的state参数类型

我今天碰见的问题

今天使用ant-design-chart,库里没有把Ref的定义导出,因此只能本身取了

// 已知
type ref = React.MutableRefObject<G2plotStackedBar | undefined>;
// 求 ???
const chartRef = useRef<???>()
复制代码

有了上面的学习,这里就很简单了,只须要取出React.MutableRefObject里的内容,一行infer搞定

// infer推断
type ChartRef<T> = T extends React.MutableRefObject<infer P> ? P : never;
                                                    
const chartRef = useRef<ChartRef<ref>>()
复制代码

总结

infer是很是有用的,若是想要摆脱仅仅是在写带类型的javascript,高级特性必定要了解

我可能一年前就看见infer了,一直没有好好学,缘由除了本身懒,还有就是水平确实不够,今年再学明显感受不一样了。

再推荐一篇很好的文章,我也是看了这篇文章才好好学习了一下infer,这篇文章讲的更复杂一点

Vue3 跟着尤雨溪学 TypeScript 之 Ref 类型从零实现

题外话 分享一道比较复杂的练习题

原题就不贴出了,在这里能够看见 github

分享一下个人思路

  1. 首先先取得函数的名字,经过extends关键字能够判断是不是函数,是返回键名,不是返回never,最后使用映射类型[keyof T]的方式来获取键名的联合类型,由于never和任何类型组联合类型都会过滤掉never,因此天然排除了never
  2. 就用infer硬推

题解以下:

type EffectModuleFuncName = {
  [K in keyof EffectModule]: EffectModule[K] extends Function ? K : never;
}[keyof EffectModule];

type UnPackedPromise<T> = T extends Promise<infer P> ? P : T;

type EffectModuleFunc<T> = T extends (params: infer P) => infer U
  ? P extends Promise<infer R>
    ? (v: R) => UnPackedPromise<U>
    : P extends Action<infer X>
    ? (v: X) => UnPackedPromise<U>
    : never
  : never;

// 修改 Connect 的类型,让 connected 的类型变成预期的类型
type Connect = (
  module: EffectModule,
) => { [K in EffectModuleFuncName]: EffectModuleFunc<EffectModule[K]> };
复制代码

也不知道本身写的对不对,总以为怪怪的,能够讨论一下


参考资料:

  1. TypeScript文档
  2. 深刻理解TypeScript

最后,祝你们身体健康,工做顺利!

欢迎你们关注个人公众号~

相关文章
相关标签/搜索