跳到主要内容

extens用法

前言

extends 是 TypeScript 类型系统关键所在,但熟练之后没必要细究,故先总结再分析。

使用场景

  • 用于类型组合
  • 用于类继承
  • 用于条件类型(Conditional Types)
  • 用于泛型约束

1.用于类型组合

interface Name {
name: string;
}

interface Sex {
sex: 'male' | 'female';
}

// 多个组合用,隔离
interface Pet extends Name, Sex {
age: number;
}

// 合法
const dog: Pet = {
name: '',
sex: 'male',
age: 8,
};
场景示例

react 子组件 props 提升公共 state 时对 props 进行聚合

2.用于继承

class Control {
private state: any;
constructor(intialValue: number) {
if (intialValue > 10) {
this.state = false;
} else {
this.state = true;
}
}
checkState() {
return this.state;
}
}

interface SelectableControl extends Control {
select(): void;
}

// 下面的代码会报错:Class 'DropDownControl' incorrectly implements interface
// 'SelectableControl'.
// Types have separate declarations of a private property 'state'.(2420)
class DropDownControl implements SelectableControl {
private state = false;
checkState() {
// do something
}
select() {
// do something
}
}

如果某个 interface A 继承了某个 class B,那么这个 interface A 还是能够被其他 interface 去继承(或者说组合)。但是,如果某个 class 想要 implements 这个 interface A,那么这个 class 只能是 class B 本身或者 class B 的子类。

要想解决这个问题,class DropDownControl必须要继承 Control class 或者Control class 的子类:

class Control {
private state: any;
constructor(intialValue: number) {
if (intialValue > 10) {
this.state = false;
} else {
this.state = true;
}
}
checkState() {
return this.state;
}
}

interface SelectableControl extends Control {
select(): void;
}

// 下面的代码就不会报错,且能得到预期的运行结果
class DropDownControl extends Control implements SelectableControl {
// private state = false;
//checkState(){
// do something
//}
select() {
// do something
}
}

const dropDown = new DropDownControl(1);
dropDown.checkState(); // Ok
dropDown.select(); // Ok

3.用于条件类型

interface Animal {
eat(): void;
}

interface Dog extends Animal {
bite(): void;
}

type UselessType = Dog extends Animal ? true : false; //true

extends 用来条件判断的语法和 JS 的三元表达是很相似。问题是,extends 判断条件真假的逻辑是什么?答案是类型兼容,这块需要单独理解什么是类型兼容,可以参考官方文档,这里我们要明确:在条件类型场景下 extends 是结合? ,:一起使用的

要特别注意,在泛型中会使用“分配率”,如果结果不符你的预期,你得知道发生了什么

type A = 'x' | 'y' extends 'x' ? string : number; // number
type P<T> = T extends 'x' ? string : number;
type T<K> = K extends 'x' | 'y' ? string : number;
type B = P<'x' | 'y'>; // string | number P<'x' | 'y'> => P<'x'> | P<'y'>
type C = T<'x' | 'y'>; // string
type D = P<never>; // never

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

提示
  • 满足两个要点即可适用分配律:第一,参数是泛型类型,第二,代入参数的是联合类型
  • 特殊的 never : never 被认为是空的联合类型,也就是说,没有联合项的联合类型,所以还是满足上面的分配律,然而因为没有联合项可以分配,所以 P<T>的表达式其实根本就没有执行,所以 D 的定义也就类似于永远没有返回的函数一样,是 never 类型的

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

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

思考

type UselessType<T extends number> = T;
type ResIsAny = UselessType<any>; // any 是 number 的父集,为什么它能赋值给 number 类型的值?

高级类型使用

Exclude

ExcludeTS 中的一个高级类型,其作用是从第一个联合类型参数中,将第二个联合类型中出现的联合项全部排除,只留下没有出现过的参数。

信息

从参数一中排除公共部分

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

Exclude 的定义是

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

这个定义就利用了条件类型中的分配原则,来尝试将实例拆开看看发生了什么:

type A = `Exclude<'key1' | 'key2', 'key2'>`;
// 等价于
type A = `Exclude<'key1', 'key2'>` | `Exclude<'key2', 'key2'>`;
// =>
type A = ('key1' extends 'key2' ? never : 'key1') | ('key2' extends 'key2' ? never : 'key2');
// =>
// never是所有类型的子类型
type A = 'key1' | never;

Extract

Extract和上面的 Exclude 刚好相反,它是将第二个参数的联合项从第一个参数的联合项中提取出来,当然,第二个参数可以含有第一个参数没有的项。

信息

提取参数一,参数二公共部分

type Extract<T, U> = T extends U ? T : never;
type A = Extract<'key1' | 'key2', 'key1'>; // 'key1'
type B = Extract<'key1' | 'key2', 'key1' | 'key3'>; // 'key1'

Pick

Pick 的意思是,从接口 T 中,将联合类型 K 中涉及到的项挑选出来,形成一个新的接口,其中K extends keyof T则是用来约束K 的条件,也就是说,K 的联合项必须来自接口 T 的属性。

type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
interface A {
name: string;
age: number;
sex: number;
}
type A1 = Pick<A, 'name' | 'age'>;
type A2 = Pick<A, 'name' | 'noSuchKey'>; // 报错:类型“"key" | "noSuchKey"”不满足约束“keyof A”
let b1: A1 = {
name: '1',
age: 18,
};

参考链接