介绍 extends 条件类型用法和 infer 搭配进行类型推断
extends 条件类型
基本语法
extends
条件类型有点类似三元运算符,下面来看一下它的基本用法。
interface Animal {
live(): void;
}
interface Dog extends Animal {
woof(): void;
}
// Dog 类型是 Animal 的子类型?true 则返回 number,否者返回 string
type Example1 = Dog extends Animal ? number : string; // number
// RegExp 类型是 Animal 的子类型?true 则返回 number,否者返回 string
type Example2 = RegExp extends Animal ? number : string; // string
important
判断一个对象类型是否为另一个对象类型的子类型,只需要看这个类型是否涵盖了父类型的所有属性。
因此,子类型 的属性数量一定是大于或者等于 父类型 的属性数量的。
条件类型的强大之处在于配合泛型一起使用,下面来看一个案例。
interface IdLabel {
id: number /* some fields */;
}
interface NameLabel {
name: string /* other fields */;
}
function createLabel(id: number): IdLabel;
function createLabel(name: string): NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel {
if(typeof nameOrId === 'number') return {id: nameOrId};
else return {name: nameOrId};
}
这里我们使用了 TS 中的 函数重载 机制,可以根据传入参数的类型来决定返回值。
let a = createLabel(1); // IdLable
let b = createLabel("1"); // NameLable
let c = createLabel(Math.random() ? "hello" : 42); // IdLable | NameLable
实际上我们可以使用 extends
条件类型来简化函数重载。
// T 如果是 number 类型则返回 IdLabel 类型,否者返回 NameLabel 类型
type NameOrId<T extends number | string> = T extends number ? IdLabel : NameLabel;
function createLabel<T extends number | string>(nameOrId: T): NameOrId<T> {
return (typeof nameOrId === "number") ? {id: nameOrId} as NameOrId<T> : {name: nameOrId} as NameOrId<T>;
}
let a = createLabel(1); // IdLable
let b = createLabel("1"); // NameLable
let c = createLabel(Math.random() ? "hello" : 42); // IdLable | NameLable
条件类型约束
先看一个泛型约束的基本用法例子。
// 获取对象类型中 message 属性的类型
type MessageOf<T extends { message: unknown }> = T["message"];
interface Email {
message: string;
}
type EmailMessageContents = MessageOf<Email>;
我们使用 MessageOf
时,要求传入的泛型参数必须带有 message
属性,否者会编译报错。
现在实现一个需求:让 MessageOf
的泛型参数可以为任意类型,具有 message
属性则返回该属性的类型,不具有 message
属性则返回 never
类型。这个需求可以使用条件泛型约束实现。
type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;
条件类型在联合类型中的使用
上面所有的例子都是对象类型和条件类型搭配使用,还有一个常见的用法就是在联合类型中使用条件类型。
T extends U ? X : Y
这里的 T
和 U
都是联合类型,把联合类型看作是一个集合,整个过程中会把 T
中的每一个子类型拿出来和 U
集合进行比较,判断 T
中的子类型是否为 U
集合的子集,如果是则返回 X
,否者返回 Y
,最终会把所有返回的结果重新进行联合。
type P<T> = T extends 0 | 1 ? "A" : "B";
type A = P<0 | 1 | 2>; // type A = "A" | "B";
整个过程就是:
- 判断
0
是否为0 | 1
的子集,返回"A"
- 判断
1
是否为0 | 1
的子集,返回"A"
- 判断
2
是否为0 | 1
的子集 ,返回"B"
- 最后将所有返回结果联合起来,
"A" | "A" | "B"
即"A" | "B"
important
上面的过程 当且仅当使用泛型时,联合类型的条件判断才会具有一个分发的过程,如果把 T
写为一个固定的联合类型:
type P = 0 | 1 | 2 extends 0 | 1 ? "A" : "B"; // type A = "B"
此时会把 0 | 1 | 2
和 0 | 1
都看作一个整体,直接判断 0 | 1 | 2
是否为 0 | 1
的子集,如果是则返回 "A"
,否者返回 "B"
,所以这里的类型 P
实际上是一个字面量类型 "B"
。
使用泛型是如果想要避免分发过程,将泛型参数看作一个整体,可以加上 []
。
type P<T> = [T] extends 0 | 1 ? "A" : "B";
type A = P<0 | 1 | 2>; // type A = "B";
下面我们来实现几个应用,利用条件类型实现联合类型的交集、差集。
先明确一下概念:
- 交集:
T
中的元素能在U
集合中找到,所有的这些元素形成的集合就是T
与U
的交集 - 差集:
T
中的元素能在U
集合中找到,从T
集合中去掉这些元素后就是T
差U
type Filter<T, U> = T extends U ? T : never; // 交集
type Diff<T, U> = T extends U ? never : T; // 差集
TS 中也提供了这两种需求的泛型工具类型:Extract 交集 和 Exclude 差集。
使用 infer 在条件类型中推断
基本语法
我们先来实现一个需求,获取传入的数组类型的元素类型。
type Flatten<T> = T extends unknown[] ? T[number] : T;
let num: Flatten<number[]> = 1; // num 是 number 类型
这里我们使用了数组的类型索引机制 T[number]
,可以返回数组的元素类型。 下面我们通过 infer
来实现这个需求。
type Flatten<T> = T extends Array<infer R> ? R : T;
这里的 infer R
相当于在条件类型中定义了一个新的类型 R
,这里可以理解为如果 T
是一个数组类型,那么就会将该数组类型的元素类型赋值给 R
,然后返回 R
,否者返回 T
。
important
T extends U ? X : Y
infer
语句只能在条件类型 extends
的 U
位置处使用,作用是定义一个类型 R
。
如果条件满足则会根据类型 U
给 R
赋值,上面的例子中是根据 T = Array<R>
这个表达式,给 R
类型赋值的,比如这里 T
如果是 string[]
,那么 R
就会被赋值为 string
类型。
还需要记住一点,R
只能在条件判断为 true
的分支中使用。
分析 ReturnType 与 Parameters
TS 官方提供了有关函数类型的两个泛型工具类型:
ReturnType<T>
,用于获取一个函数类型的返回值类型Parameters<T>
,用于获取一个函数类型的所有参数类型,最后将参数类型作为元组返回
这两个泛型工具类型很典型的使用了 extends
条件类型与 infer
类型推断,下面来分析一下它们的源码。
ReturnType
type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;
在泛型参数列表中使用 T extends (...args: any[]) => any
约束泛型参数必须是一个函数类型,然后再通过 T extends (...args: any[]) => infer R
,将函数类型 T
的返回值类型赋值给 infer R
,最后返回 R
。
Parameters
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
在泛型参数列表中使用 T extends (...args: any) => any
约束泛型参数必须是一个函数类型,然后再通过 T extends (...args: infer P) => any
,利用 函数的剩余参数 将所有参数类型封装到一个元组中并赋值给 infer P
,最后返回 P
。