【TS】1321- 学会 TS infer,写起泛型真香!

共 3048字,需浏览 7分钟

 ·

2022-05-16 04:22

阿宝哥精心准备的《轻松学 TypeScript》 视频教程已经更新到第十一期了,通过形象生动的动画,让你轻松搞懂 TypeScript 的难点和核心知识点!

你知道如何获取 T0 数组类型中元素的类型和 T1 函数类型中的返回值类型么?给你 3 秒钟的时间思考一下。

type T0 = string[];
type T1 = () => string;

要实现上述的功能,我们可以使用 TypeScript 提供的类型模式匹配技术 —— 条件类型 + infer。条件类型允许我们检测两种类型之间的关系,通过条件类型我们就可以判断两种类型是否相兼容。而 infer 用于声明类型变量,以存储在模式匹配过程中所捕获的类型。

下面我们来看一下如何捕获 T0 数组类型中元素的类型:

type UnpackedArray = T extends (infer U)[] ? U : T
type U0 = UnpackedArray // string

在以上代码中,T extends (infer U)[] ? U : T 是条件类型的语法,而 extends 子句中的 infer U 引入了一个新的类型变量 U,用于存储被推断的类型。

为了便于大家的理解,我们来演示一下 UnpackedArray 工具类型的执行流程。

type U0 = UnpackedArray

// T => T0: string[]
type UnpackedArray<string[]> = string[] extends (infer U)[] ? U : string[] 
// string[] extends (infer U)[] 模式匹配成功
// U => string 

需要注意的是,infer 只能在条件类型的 extends 子句中使用,同时 infer 声明的类型变量只在条件类型的 true 分支中可用。

type Wrong1extends (infer U)[]> = T[0// Error
type Wrong2 = (infer U)[] extends T ? U : T // Error
type Wrong3 = T extends (infer U)[] ? T : U // Error

了解完这些知识之后,我们来看一下如何获取 T1 函数类型的返回值类型:

type UnpackedFn = T extends (...args: any[]) => infer U ? U : T;

type U1 = UnpackedFn// string

看完 UnpackedFn 工具类型的实现,是不是觉得挺简单的。当遇到函数重载的场景,TypeScript 将使用最后一个调用签名进行类型推断:

declare function foo(x: string): number;
declare function foo(x: number): string;
declare function foo(x: string | number): string | number;

type UnpackedFn = T extends (...args: any[]) => infer U ? U : T;
type U2 = UnpackedFn<typeof foo>;  // string | number

如果你对 TypeScript 的条件类型还不了解的话,建议观看 “用了 TS 条件类型,同事直呼 YYDS” 这篇文章。在该篇文章中,我们介绍了条件链,利用条件链我们可以实现功能更加强大的 Unpacked 工具类型。

type Unpacked =
    T extends (infer U)[] ? U :
    T extends (...args: any[]) => infer U ? U :
    T extends Promise ? U :
    T;

type T0 = Unpacked<string>;  // string
type T1 = Unpacked<string[]>;  // string
type T2 = Unpacked<() => string>;  // string
type T3 = Unpacked<Promise<string>>;  // string
type T4 = Unpacked<Promise<string>[]>;  // Promise
type T5 = UnpackedPromise<string>[]>>;  // string

在以上代码中,Unpacked 工具类型利用了条件类型和条件链,轻松实现了推断出数组类型中元素的类型、函数类型返回值的类型和 Promise 类型中返回值的类型的功能。

其实,利用条件类型和 infer,我们还可以推断出对象类型中键的类型。接下来,我们来举个具体的例子:

type User = {
  id: number;
  name: string;
}

type PropertyType =  T extends { id: infer U, name: infer R } ? [U, R] : T
type U3 = PropertyType // [number, string]

在 PropertyType 工具类型中,我们通过 infer 声明了两个类型变量 U 和 R,分别表示对象类型中 id 和 name 属性的类型。若类型匹配,我们就会以元组的形式返回 id 和 name 属性的类型。

那么现在问题来了,在 PropertyType 工具类型中,如果只声明一个类型变量 U,那结果又会是怎样呢?下面我们来验证一下:

type PropertyType =  T extends { id: infer U, name: infer U } ? U : T

type U4 = PropertyType // string | number

由以上代码可知,U4 类型返回的是 string 和 number 类型组合成的联合类型。为什么会返回这样的结果呢?这是因为在协变位置上,若同一个类型变量存在多个候选者,则最终的类型将被推断为联合类型。

然而,在逆变位置上,若同一个类型变量存在多个候选者,则最终的类型将被推断为交叉类型。同样,我们来实际验证一下:

type Bar = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never;

type U5 = Bar<{ a: (x: string) => void, b: (x: number) => void }>;  // string & number

在以上代码中,U5 类型返回的是 string 和 number 类型组合成的交叉类型,即最终的类型是 never 类型。

看完本文之后,相信你已经了解条件类型和 infer 的作用了。那么你能看懂 UnionToIntersection 这个工具类型的具体实现么?

type UnionToIntersection = (
  U extends any ? (arg: U) => void : never
extends (arg: infer R) => void
  ? R
  : never

扫码查看 轻松学 TypeScript 系列视频教程

(目前已更新 11 期)

关于前面提到的协变和逆变,阿宝哥将在后续的文章中介绍,感兴趣的话记得关注阿宝哥。你喜欢以这种形式学 TS 么?喜欢的话,记得点赞与收藏哟。

浏览 14
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报