乐趣区

关于javascript:精读Typescript-infer-关键字

Infer 关键字用于条件中的类型推导。

Typescript 官网也拿 ReturnType 这一经典例子阐明它的作用:

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

了解为:如果 T 继承了 extends (...args: any[]) => any 类型,则返回类型 R,否则返回 any。其中 R 是什么呢?R 被定义在 extends (...args: any[]) => infer R 中,即 R 是从传入参数类型中推导进去的。

精读

咱们能够从两个视角来了解 infer,别离是需要角度与设计角度。

需要角度了解 infer

实现 infer 这个关键字肯定是背地存在需要,这个需要是一般 Typescript 能力无奈满足的。

构想这样一个场景:实现一个函数,接管一个数组,返回第一项。

咱们无奈用泛型来形容这种类型推导,因为泛型类型是一个整体,而咱们想要返回的是入参其中某一项,咱们并不能通过相似 T[0] 的写法拿到第一项类型:

function xxx<T>(...args: T[]): T[0]

而实际上不反对这种写法也是正当的,因为这次是获取第一项类型,如果 T 是一个对象,咱们想返回其中 onChange 这个 Key 的返回值类型,就不晓得如何书写了。所以此时必须用一种新的语法实现,就是 infer

设计角度了解 infer

从类型推导性能来看,泛型性能十分弱小,咱们能够用泛型形容调用时才传入的类型,并提前将它形容在类型表达式中:

function xxx<T>(value: T): {result: T}

但咱们发现 T 这个泛型太整体化了,咱们还不具备从中 Pick 子类型的能力。也就是对于 xxx<{label: string}> 这个场景,T = {label: string},但咱们无奈将 R 定义为 {label: R} 这个地位,因为泛型是一个不可拆分的整体。

而且实际上为了类型平安,咱们也不能允许用户形容任意的类型地位, 万一传入的类型构造不是 {label: xxx} 而是一个回调 () => void,那子类型推导岂不是建设在了谬误的环境中。 所以思考到想要拿到 {label: infer R},首先参数必须具备 {label: xxx} 的构造,所以正好能够将 infer 与条件判断 T extends ? A : B 联合起来用,即:

type GetLabelTypeFromObject<T> = T extends ? {label: infer R} ? R : never

type Result = GetLabelTypeFromObject<{label: string}>;
// type Result = string

即如果 T 遵循 {label: any} 这样一个构造,那么我能够将这个构造中任何变量地位替换为 infer xxx,如果传入类型满足这个构造(TS 动态解析环节判断),则能够基于这个构造体持续推导,所以在推导过程中咱们就能够应用 infer xxx 推断的变量类型。

回过头来看第一个需要,拿到第一个参数类型就能够用 infer 实现了:

type GetFirstParamType<T> = T extends ? (...args: infer R) => any ? R[0] : never

能够了解为,如果此时 T 满足 (...args: any) => any 这个构造,同时咱们用 infer R 示意 R 这个长期变量指代第一个 any 运行时类型,那么整个函数返回的类型就是 R。如果 T 都不满足 (...args: any) => any 这个构造,比方 GetFirstParamType<number>,那这种推导基本无从谈起,间接返回 never 类型兜底,当然也能够自定义比方 any 之类的任何类型。

概述

咱们了解了 infer 含意后,再联合 conditional infer 这篇文章了解外面的例子,有助于加深记忆。

type ArrayElementType<T> = T extends (infer E)[] ? E : T;
// type of item1 is `number`
type item1 = ArrayElementType<number[]>;
// type of item1 is `{name: string}`
type item2 = ArrayElementType<{name: string}>;

能够看到,ArrayElementType 利用了条件推断与 infer,示意了这样一个逻辑:如果 T 类型是一个数组,且咱们将数组的每一项定义为 E 类型,那么返回类型就为 E,否则为 T 整体类型自身。

所以对于 item1 是满足构造的,所以返回 number,而 item2 不满足构造,所以返回其类型自身。

特地补充一点,对于上面的例子返回什么呢?

type item3 = ArrayElementType<[number, string]>;

答案是 number | string,起因是咱们用多个 infer E(infer E)[] 相当于 [infer E, infer E]... 不就是多个变量指向同一个类型代词 E 嘛)同时接管到了 numberstring,所以能够了解为 E 时而为 number 时而为 string,所以是或关系,这就是协变。

那如果是函数参数呢?

type Bar<T> = T extends {a: (x: infer U) => void; b: (x: infer U) => void }
  ? U : never
type T21 = Bar<{a: (x: string) => void; b: (x: number) => void }>; // string & number

发现后果是 string & number,也就是逆变。但这个例子也是同一个 U 时而为 string 时而为 number 呀,为什么是且的关系,而不是或呢?

其实协变或逆变与 infer 参数地位无关。在 TypeScript 中,对象、类、数组和函数的返回值类型都是协变关系,而函数的参数类型是逆变关系,所以 infer 地位如果在函数参数上,就会遵循逆变准则。

逆变与协变:

  • 协变 (co-variant):类型收敛。
  • 逆变 (contra-variant):类型发散。

对于逆变与协变更深刻的话题能够再开一篇文章了,这里就不细讲了,对于 infer 了解到这里就够啦。

总结

infer 关键字让咱们领有深刻开展泛型的构造,并 Pick 出其中任何地位的类型,并作为长期变量用于最终返回类型的能力。

对于 Typescript 类型编程,最大的问题莫过于心愿实现一个成果却不晓得用什么语法,infer 作为一个弱小的类型推导关键字,势必会在大部分简单类型推导场景下派上用场,所以在遇到困难时,能够想想是不是能用 infer 解决问题。

探讨地址是:精读《Typescript infer 关键字》· Issue #346 · dt-fe/weekly

如果你想参加探讨,请 点击这里,每周都有新的主题,周末或周一公布。前端精读 – 帮你筛选靠谱的内容。

关注 前端精读微信公众号

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

版权申明:自在转载 - 非商用 - 非衍生 - 放弃署名(创意共享 3.0 许可证)

退出移动版