TypeScript 学习笔记(四)- extends 的作用

一、继承

接口继承
  interface T1 {
    name: string
  }
  
  interface T2 {
    age: number
  }
  
  // 多重继承,逗号隔开
  interface T3 extends T1,T2 {
    gender: string
  }
 // T3 => { name: string; age: number, gender: string }
  
  const t3: T3 = {
    name: '111',
    gender: '1',
    age: 1
  }

T3除了自己的属性外,还同时拥有了来自T1和T2的属性

继承父类的方法和属性
class Animal {
  name = "animal";
  constructor(name: string) {
    this.name = name;
  }

  getName() {
    console.log(this.name);
  }
}

class Dog extends Animal {
  getAction() {
    console.log("run");
  }
}

const dog = new Dog("dog");
dog.getName(); // "dog"
dog.getAction();  // "run"

Dog 继承了父类的 getName 方法,可以在实例 dog 上直接调用。

二、条件判断

条件判断就是用来判断一个类型是不是可以分配给另一个类型,这在写高级类型的时候非常有用。

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

这是因为 Animal 和 Dog 的类型完全相同,或者说 Animal 类型的一切约束条件,Dog 都具备;换言之,类型为 Animal 的值可以分配给类型为 Dog 的值,反之亦然。

需要理解的是,这里A extends B,是指类型A可以分配给类型B,而不是说类型A是类型B的子集。

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

当我们给Dog加上一个action属性,发现此时Bool是’no’,这是因为 Animal 没有类型为string的action属性,类型Animal不满足类型Dog的类型约束。

因此,A extends B,是指类型A可以分配给类型B,而不是说类型A是类型B的子集,理解extends在类型三元表达式里的用法非常重要。

再看一个例子

interface Animal {
  name: string;
}

interface Dog extends Animal {
  action: string;
}

  type Bool = Animal extends Dog ? 'yes' : 'no'; //  'no'
  type Bool = Dog extends Animal ? 'yes' : 'no'; //  'yes'

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

上面的示例中,Dog是Animal的子类,父类比子类的限制更少,能满足子类,则一定能满足父类,Dog类型的值可以赋值给Animal类型的值,判断为真。

三、泛型条件判断

  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

A1和A2是extends条件判断的普通用法,和上面的判断方法一样。

P是带参数T的泛型类型,其表达式和A1,A2的形式完全相同,A3是泛型类型P传入参数’x’ | 'y’得到的类型,A3和A2的类型相似,但是结果不同,出现这个结果的原因是所谓的分配条件类型

对于使用extends关键字的条件类型(即上面的三元表达式类型),如果extends前面的参数是一个泛型类型,当传入该参数的是联合类型,则使用分配律计算最终的结果。分配律是指,将联合类型的联合项拆成单项,分别代入条件类型,然后将每个单项代入得到的结果再联合起来,得到最终的判断结果。

上面的例子可以拆分

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

总之,满足两个要点即可适用分配律:

  1. 参数是泛型类型,
  2. 代入参数的是联合类型

在泛型的条件判断中还有些特殊的情况

特殊的never

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

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

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

防止条件判断中的分配

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

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

四、泛型约束

在书写泛型的时候,我们往往需要对类型参数作一定的限制。

interface Person {
  name: string;
  age: number;
  gender: string;
}

class Student {
  constructor(private info: Person) {}

  getInfo<T extends keyof Person>(key: T): Person[T] {
    return this.info[key];
  }
}

const student = new Student({
  name: 'uuuu',
  age: 20,
  gender: 'male'
})
const test = student.getInfo('name');
console.log(test) // "uuuu"

这里extends对传入的参数作了一个限制,参数必须是 Person的成员名的联合类型,避免传入其他key。

参考链接
Typescript extends使用
TS关键字extends用法总结

上一篇:类com.lu.servlet.HelloServlet不是Servlet


下一篇:JAVA 一些方法