Keep calm & thinking

ts 中的 extends

背景

很多内置的方法譬如 Pick / Exclude / Extract 等都是使用 extends 实现,是理解类型的一个关键。

在语言逻辑层面,可以理解为继承,譬如 class extends。

但是在语言类型层面,表示“约束”。

类型

如何解释是“约束”?通过几个场景阐述:

  • 泛型
  • 条件判断
  • 泛型+条件判断

泛型约束

用来限制必须传某些属性

interface Dispatch<T extends { type: string }> {
  (action: T): T
}

条件判断

场景有些类似三目运算符

SomeType extends OtherType ? TrueType : FalseType;

这里表示类型 OtherType 约束 SomeType,举例:

type Animal = {
  name: string;
  occupation: string;
}
type Cat = {
  name: string;
}
type Bool = Animal extends Cat ? 'yes' : 'no'; // yes

泛型+条件判断

是指定义一个泛型,其中使用了条件判断的情况

type P<T> = T extends 'x' ? string : number;
type A3 = P<'x' | 'y'> // A3 的类型是?

当传入的是联合类型时,A3 也是联合类型,实际会将联合类型的联合项拆成单项分别代入条件类型,再将结果联合起来。

高级类型

必填取反

是指将一个类型下必填和选填取反得出一个新类型

// 如何将 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>

结果

type PersonReverse = {
	name?: string | undefined
  sex?: string | undefined
}

原理

// 核心逻辑
{} extends Pick<T, K> ? never : K

使用 as 类型断言为空对象类型,然后 Pick 出 T 中的属性,用来约束空对象类型,Pick 过程如下:

// 步骤1
{} extends { name: string } // false

// 步骤2
{} extends { age?: number } // true

// 步骤3
{} extends { sex: number } // false

Exclude

type Exclude<T, U> = T extends U ? never : T

type A = Exclude<'key1' | 'key2', 'key2'> // key1

Extract

type Extract<T, U> = T extends U ? T : never

type A = Extract<'key1' | 'key2', 'key1'> // 'key1'

引用

深入理解Typescript中的extends关键字

上次更新: