TypeScript中extends用法小结

本博客 hjy-xh,转载请申明出处

extends关键字在TS中有多种用法,单独使用时,常见有以下几种情况

  • 继承/拓展
  • 约束
  • 条件判断

继承/扩展

单个/多个接口

这种用法和类的继承相似,看个🌰

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Animal {
name: string;
}

// Dog继承Animal并扩展一个run方法
interface Dog extends Animal {
run: () => void;
}

const dog: Dog = {
name: '狗子',
run: () => {}
}

也支持多重继承

1
2
3
interface Blackdog extends Animal, Dog {
color: string;
}

扩展类的接口

除了上面的常见用法,接口还可以继承类的私有成员和受保护成员,而不仅仅是公共成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Animal {
private state: boolean;
}

interface Runnable extends Animal {
run(): void
}

class Dog extends Animal implements Runnable {
run() { }
}
class Cat extends Animal implements Runnable {
run() { }
}
class Bird extends Animal { }


// Error: 缺少属性 state
class Monkey implements Runnable {
run() { }
}

小结

  • 一个接口可以继承/扩展一个或多个现有接口
  • 一个接口也可以继承/扩展一个类。如果该类包含私有或受保护成员,则该接口只能由该类或其子类实现

约束

此处说的约束,一般指泛型约束,即对泛型的类型进行约束控制

在编写方法的时候,可能会需要对参数的类型做一些限制,比方说入参有一个length属性

1
2
3
4
5
6
7
8
9
10
11
// 没有约束
function getLength<T>(arg: T): T {
// Error: 类型“T”上不存在属性“length”
return arg.length;
}

// 进行约束
function getLength<T extends {length: number}>(arg: T): number {
return arg.length;
}

条件判断

和三目表达式类似,看看官网的示例和说明

1
SomeType extends OtherType ? TrueType : FalseType;

When the type on the left of the extends is assignable to the one on the right, then you’ll get the type in the first branch (the “true” branch); otherwise you’ll get the type in the latter branch (the “false” branch).

如果extends前面的类型能够赋值给extends后面的类型,那么表达式判断为真,否则为假

常规使用

1
2
3
4
5
6
7
8
9
10
11
12
13
interface A1 {
name: string
}

interface A2 {
name: string
age: number
}
// A的类型为string
type A = A2 extends A1 ? string : number

const str: A = 'this is string'

A1,A2两个接口,满足A2的接口一定可以满足A1,所以条件为真,A的类型取string

结合泛型使用

1
2
3
4
5
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'> // ?

A1、A2的结果也就是常规的用法,很容易得出结果,但是A3结合了泛型,这里有一个Distributive Conditional Types的概念

When conditional types act on a generic type, they become distributive when given a union type

如果extends前面的参数是一个泛型类型,当传入该参数的是联合类型,则使用分配律计算最终的结果。分配律是指,将联合类型的联合项拆成单项,分别代入条件类型,然后将每个单项代入得到的结果再联合起来,得到最终的判断结果。

以上面的A3为例,进行推导

P中T是一个泛型参数。在A3的定义中,给T传入的是’x’和’y’的联合类型’x’ | ‘y’,满足分配律,于是’x’和’y’被拆开,分别代入P

1
2
3
4
5
P<'x' | 'y'> => P<'x'> | P<'y'>
// 'x'代入得到
'x' extends 'x' ? string : number => string
// 'y'代入得到
'y' extends 'x' ? string : number => number

然后将每一项代入得到的结果联合起来,得到string | number

满足两个要点即可适用分配律:第一,参数是泛型类型,第二,代入参数的是联合类型

特殊的never

1
2
3
4
5
// never是所有类型的子类型
type A1 = never extends 'x' ? string : number; // string

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

never被认为是空的联合类型,也就是说,没有联合项的联合类型,所以还是满足上面的分配律,然而因为没有联合项可以分配,所以P<T>的表达式其实根本就没有执行,所以A2的定义也就类似于永远没有返回的函数一样,是never类型的

防止条件判断中的分配

1
2
3
type P<T> = [T] extends ['x'] ? string : number;
type A1 = P<'x' | 'y'> // number
type A2 = P<never> // string

在条件判断类型的定义中,将泛型参数使用[]括起来,即可阻断条件判断类型的分配,此时,传入参数T的类型将被当做一个整体,不再分配

参考