TypeScript extends

发布时间 2023-04-17 01:56:50作者: Himmelbleu

extends 的含义

extends 在接口和类中都是继承的意思,继承之后的子接口或子类都是属于这个父接口或父类的,是一个从属关系。

extends 还有一个重要的用法就是在类型中,依旧还是继承的意思,是一种从属关系。如下的例子所示:

interface Animal {
    name: string;
    age: number;
}

interface Dog extends Animal {
    run: () => void;
}

type A = Dog extends Animal ? string : number
//   ^? string

判断 Dog 是否属于 Animal 类型,如果属于就返回 string 类型给 A,否则返回 number 给 A。Dog 接口继承了 Animal 接口,所以 Dog 属于 Animal,因此,A 得到的是一个 string 类型。

下面还有一个例子,可能反直觉:

interface Animal {
    name: string;
    age: number;
}

interface Dog {
    name: string;
    age: number;
    run: () => void;
}

type A = Dog extends Animal ? string : number
//   ^? string

A 得到的是一个 string 类型。接口不管你有没有使用 extends 继承,只要 Dog 接口包括了 Animal 接口的全部属性,Dog 就是属于 Animal 的,因此 A 得到的是一个 string。

分配条件类型

type A1 = 'x' extends 'x' ? string : number;
//   ^? string
type A2 = 'x' | 'y' extends 'x' ? string : number;
//   ^? number

type P<T> = T extends 'x' ? string : number;
type A3 = P<'x' | 'y'>
//   ^? string | number

A3 得到的类型是 string | number 联合类型,这是因为分配条件类型

在条件类型判断的情况下(上边示例中出现的 extends),如果入参是联合类型,则会被拆解成一个个独立的类型进行类型运算,每一个独立的运算的结果处理成一个联合类型。

A3 的联合类型可以看作下面这样的运算得到:

type A3 = ('x' extends 'x' ? string : number) | ('y' extends 'x' ? string : number);
//   ^? string | number

给 P 类型传递的参数类型 T,即 'x' | 'y',满足“分配条件类型”的规则,因此 A3 得到的是一个 string | number 联合类型。

阻止分配条件类型

阻止发生“分配条件类型”,可以对参数类型和 extends 后面的类型都加上 []

type P<T> = [T] extends ['x'] ? string : number;
type A3 = P<'x' | 'y'>
//   ^? number

never 类型

还是上面的例子,左边的类型换成 never:

type A1 = never extends 'x' ? string : number;
//   ^? string

type P<T> = T extends 'x' ? string : number;
type A2 = P<never>
//   ^? never

A1 不符合“分配条件类型”规则,never 可以是 'x' 的子类型,因此,A1 得到的类型是 string。

A2 符合“分配条件类型”规则,参数类型 T 是 never 类型,因此,A2 得到的是一个 never 类型。

阻止分配条件类型

阻止发生“分配条件类型”,可以对参数类型和 extends 后面的类型都加上 []

type P<T> = [T] extends ['x'] ? string : number;
type A2 = P<never>
//   ^? string

A2 不符合“分配条件类型”规则,never 可以是 'x' 的子类型,因此,A2 得到的类型是 string。