ts 中的 extends
背景
很多内置的方法譬如 Pick / Exclude / Extract 等都是使用 extends 实现,是理解类型的一个关键。
在语言逻辑层面,可以理解为继承,譬如 class extends。
但是在语言类型层面,表示“约束”。
类型
如何解释是“约束”?通过几个场景阐述:
- 泛型
- 条件判断
- 泛型+条件判断
泛型约束
用来限制必须传某些属性
tsx
interface Dispatch<T extends { type: string }> {
(action: T): T
}
条件判断
场景有些类似三目运算符
tsx
SomeType extends OtherType ? TrueType : FalseType;
这里表示类型 OtherType 约束 SomeType,举例:
tsx
type Animal = {
name: string;
occupation: string;
}
type Cat = {
name: string;
}
type Bool = Animal extends Cat ? 'yes' : 'no'; // yes
泛型+条件判断
是指定义一个泛型,其中使用了条件判断的情况
tsx
type P<T> = T extends 'x' ? string : number;
type A3 = P<'x' | 'y'> // A3 的类型是?
当传入的是联合类型时,A3 也是联合类型,实际会将联合类型的联合项拆成单项分别代入条件类型,再将结果联合起来。
高级类型
必填取反
是指将一个类型下必填和选填取反得出一个新类型
tsx
// 如何将 Person 类型取反?
type Person = {
name: string
age?: number
sex: number
}
// 定义取反泛型
type Reverse<T> = {
[K in keyof T as {} extends Pick<T, K> ? never : K ]?: T[K]
}
type PersonReverse = Reverse<Person>
结果
tsx
type PersonReverse = {
name?: string | undefined
sex?: string | undefined
}
原理
tsx
// 核心逻辑
{} extends Pick<T, K> ? never : K
使用 as 类型断言为空对象类型,然后 Pick 出 T 中的属性,用来约束空对象类型,Pick 过程如下:
tsx
// 步骤1
{} extends { name: string } // false
// 步骤2
{} extends { age?: number } // true
// 步骤3
{} extends { sex: number } // false
Exclude
tsx
type Exclude<T, U> = T extends U ? never : T
type A = Exclude<'key1' | 'key2', 'key2'> // key1
Extract
tsx
type Extract<T, U> = T extends U ? T : never
type A = Extract<'key1' | 'key2', 'key1'> // 'key1'