TS 是鸭子类型系统,只要两个对象结构一致,就认为是同一种类型,而不需要两者的实际类型有显式的继承关系。类型兼容的目的一切都是为了安全,多从安全角度去考虑,就能理解为什么有的时候少的能赋给多的,有的时候多的能赋给少的。

联合类型

两个类型,类型少的(基础类型)可以赋给类型多的(联合类型)

  1. type NumOrStr = number | string; // 类型多
  2. let numOrStr: NumOrStr = 1; // ok 类型少 少的类型可以赋给多的

接口类型

两个接口之间,类型多的接口可以赋给类型少的接口

  1. interface Animal {
  2. name: string;
  3. age: number;
  4. }
  5. interface Cat {
  6. name: string;
  7. age: number;
  8. color: string;
  9. }
  10. let animal!: Animal // 类型少
  11. let cat!: Cat // 类型多
  12. animal = cat // ok,类型多的接口可以赋给类型少的接口
  13. cat = animal // error,类型 "Animal" 中缺少属性 "color",但类型 "Cat" 中需要该属性。
  14. let cat2: Cat = { // 类型兼容只针对类型,这里直接赋值,不会出现类型兼容
  15. name: 'cat',
  16. age: 1,
  17. color: 'black',
  18. size: 'small' // error,“size”不在类型“Cat”中
  19. }
  20. let cat3 = {
  21. name: 'cat',
  22. age: 1,
  23. color: 'black',
  24. size: 'small'
  25. }
  26. cat = cat3 // ok,为了看类型是否兼容,这里赋值的时候就会先进行类型转换

类型断言

若 A 兼容 B,那么 A 能够被断言为 B,B 也能被断言为 A。
换言之:要使得 A 能够被断言为 B,只需要 A 兼容 B 或 B 兼容 A 即可

  1. // TypeScript只会比较看它们最终的结构,发现Animal 兼容 Cat
  2. interface Animal {
  3. name: string;
  4. }
  5. interface Cat {
  6. name: string;
  7. run(): void;
  8. }
  9. function testAnimal(animal: Animal) {
  10. // 允许 animal as Cat 是因为「父类可以被断言为子类」
  11. return (animal as Cat);
  12. }
  13. function testCat(cat: Cat) {
  14. // 允许 cat as Animal 是因为既然子类拥有父类的属性和方法,那么被断言为父类,获取父类的属性、调用父类的方法,就不会有任何问题,故「子类可以被断言为父类」
  15. return (cat as Animal);
  16. }

⭐函数

参数类型与数量

逆变(Contravariant):传递参数个数,允许少的赋给多的

  1. let sum = (a: string, b: string): string => a + b
  2. const sum2 = (a: string): string => a
  3. const sum3 = (a: string, b: string): string => a + b
  4. const sum4 = (a: string, b: string, c: string): string => a + b + c // 参数个数多了
  5. sum = sum2 // ok
  6. sum = sum3 // ok
  7. sum = sum4 // error,不能传多个参数,不安全

协变(Covariant):声明参数类型,允许多的赋给少的

  1. let sum = (a: string | number): string => ''
  2. const sum2 = (a: string | number): string => ''
  3. const sum3 = (a: string | number | boolean): string => ''
  4. const sum4 = (a: string): string => '' // 类型是少的
  5. sum = sum2 // ok
  6. sum = sum3 // ok
  7. sum = sum4 // error,不能少传类型,不安全

双向协变(Bivariant):TypeScript 的“双向协变”,默认不允许。老版本允许双向协变,即函数参数既可以协变也可以逆变,这并不是类型安全的。

  1. interface Person {
  2. name: string;
  3. }
  4. interface Student extends Person {
  5. grade: number;
  6. }
  7. // 第2个参数应该消费一个 Person
  8. function doIt(
  9. provider: () => Person,
  10. customer: (v: Person) => void,
  11. ): void {
  12. const p = provider();
  13. customer(p);
  14. }
  15. const personProvider = () => ({ name: "James" } as Person);
  16. const studentProvider = () => ({ name: "Jack", grade: 7 } as Student);
  17. const personCustomer = (p: Person) => console.log(p.name);
  18. const studentCustomer = (p: Student) => console.log(`${p.name} in ${p.grade}`);
  19. doIt(personProvider, personCustomer); // OK
  20. doIt(personProvider, studentCustomer);// Error:studentCustomer需要消费一个 Student,给的是Person,那 studentCustomer 中使用 p.grade 时就会不找到 grade 属性。
  21. doIt(studentProvider, personCustomer);// OK
  22. doIt(studentProvider, studentCustomer); // Error:提供了一个 Student,但是 doIt() 是把它当作 Person 看待的(根据参数声明)

返回类型

协变(Covariant):返回个数,允许多的赋给少

  1. let point2D!: {
  2. x: number;
  3. y: number;
  4. }
  5. let point3D!: {
  6. x: number;
  7. y: number;
  8. z: number;
  9. }
  10. point2D = point3D; // ok,返回个数多的可以赋给个数少的
  11. point3D = point2D; // error,返回个数少的不能赋给个数多的

逆变(Contravariant):返回类型,允许少的赋给多的

  1. let sum!: (a: string) => string | number
  2. let sum2!: (a: string) => string
  3. let sum3!: (a: string) => string | number | boolean
  4. sum = sum2 // ok,返回类型少的可以赋给返回类型多的
  5. sum = sum3 // error,返回类型多的不可以赋给返回类型少的

可选的和 rest 参数

可选的和 Rest 参数(任何数量的参数)都是兼容的,但是可选的和必选的在strictNullChecks:false下是兼容的:

  1. let foo = (x: number, y: number) => {};
  2. let bar = (x?: number, y?: number) => {};
  3. let bas = (...args: number[]) => {};
  4. foo = bar = bas;
  5. bas = bar = foo;

枚举

  • 枚举与数字类型相互兼容 ```typescript enum Status { Ready, Waiting }

let status = Status.Ready; let num = 0;

status = num; num = status;

  1. - 来自于不同枚举的枚举变量,被认为是不兼容的:
  2. ```typescript
  3. enum Status {
  4. Ready,
  5. Waiting
  6. }
  7. enum Color {
  8. Red,
  9. Blue,
  10. Green
  11. }
  12. let status = Status.Ready;
  13. let color = Color.Red;
  14. status = color; // Error

  • 仅仅只有实例成员和方法会相比较,构造函数和静态成员不会被检查。 ```typescript class Animal { feet: number; constructor(name: string, numFeet: number) {} }

class Size { feet: number; constructor(meters: number) {} }

let a: Animal; let s: Size;

a = s; // OK s = a; // OK

  1. - 私有的和受保护的成员必须来自于相同的类。
  2. ```typescript
  3. class Animal {
  4. protected feet: number;
  5. }
  6. class Cat extends Animal {}
  7. let animal: Animal;
  8. let cat: Cat;
  9. animal = cat; // ok
  10. cat = animal; // ok
  11. class Size {
  12. protected feet: number;
  13. }
  14. let size: Size;
  15. animal = size; // ERROR
  16. size = animal; // ERROR

泛型

TypeScript 类型系统基于变量的结构,仅当类型参数在被一个成员使用时,才会影响兼容性。
如下例子中,T 对兼容性没有影响:

  1. interface Empty<T> {}
  2. let x: Empty<number>;
  3. let y: Empty<string>;
  4. x = y; // ok

当 T 被成员使用时,它将在实例化泛型后影响兼容性:

  1. interface Empty<T> {
  2. data: T;
  3. }
  4. let x: Empty<number>;
  5. let y: Empty<string>;
  6. x = y; // Error

如果尚未实例化泛型参数,则在检查兼容性之前将其替换为 any:

  1. let identity = function<T>(x: T): T {
  2. // ...
  3. };
  4. let reverse = function<U>(y: U): U {
  5. // ...
  6. };
  7. identity = reverse; // ok, 因为 `(x: any) => any` 匹配 `(y: any) => any`