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

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

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

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

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

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

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

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

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

type U0 = UnpackedArray<T0>

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

5b02ba06d737ae965a6070eedec54160.png

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

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

6df08b487d4bb8290d9e01b14a8b408d.png

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

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

type U1 = UnpackedFn<T1>; // 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> = T extends (...args: any[]) => infer U ? U : T;
type U2 = UnpackedFn<typeof foo>;  // string | number

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

type Unpacked<T> =
    T extends (infer U)[] ? U :
    T extends (...args: any[]) => infer U ? U :
    T extends Promise<infer U> ? 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<string>
type T5 = Unpacked<Unpacked<Promise<string>[]>>;  // string

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

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

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

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

94e78f2ca62931bb1e4d88f1b06bbaed.png

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

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

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

type U4 = PropertyType<User> // string | number

f41e53b6e524b42f44d4dc6aaef6ce3f.png

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

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

type Bar<T> = 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

868dbce3f6dafbac99b84264d460244d.png

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

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

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

702cc60db3705b22aec1eb78a033cfb0.png

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

ab8c608a208a1156cca59216a32247f7.png

(目前已更新 11 期)

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

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值