如何理解TS的<类型兼容性>

81 阅读4分钟

1. 接口 类型兼容性

  • typeinterface 声明的效果都是这样
  • ChildType包含ParentType的全部属性,这样的结构为 父子类型结构
  • p1=c1的赋值操称为 协变,展现形式就是增量的参数结构关系赋值
type ParentType = {
  name: string
  age: number
}
type ChildType = {
  name: string
  age: number
  sex: string
}
let p1: ParentType = {
  name: 'p1',
  age: 20
}
let c1: ChildType = {
  name: 'p1',
  age: 20,
  sex: 'man'
}
c1 = p1 // 报错:类型 "ParentType" 中缺少属性 "sex",但类型 "ChildType" 中需要该属性
p1 = c1 // 正确:在TS中允许这样的赋值

2. 函数 -- 参数 类型兼容性

  • 注意的是:参数的名字相同与否无所谓,只看它们的类型
  • y=x , 参数多的=参数少的:可以,因为是忽略类型定义,不容易导致错误
  • x=y , 参数少的=参数多的:可以,因为是增加参数定义,可能导致错误
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;

y = x; // OK: 因为`x`的每个参数必须都能在`y`里找到对应类型的参数
x = y; // Error: 因为`y`有个必需的第二个参数,而`x`没有这个参数和类型

3. 函数 -- 返回值 类型兼容性

  • m = n , 参数少的=参数多的:可以,因为是增加参数返回,必要的字段还在
  • n = m , 参数多的=参数少的:不行,因为是减少参数返回,可能导致取不到值
  • 类型系统强制源函数的返回值类型必须是目标函数返回值类型的子类型 , 也就是等号后的返回值队列需大于且包含接受赋值的函数返回值队列
let m = () => ({name: 'Alice'});
let n = () => ({name: 'Alice', location: 'Seattle'});

m = n; // OK
n = m; // Error

4. 函数 -- 可选参数及剩余参数 类型兼容性

  • 这个例子说明:剩余参数存在不确定性和不安全的特点
  • 如果可以的话,尽量还是使用接口的形式去约束函数参数
function invokeLater(args: any[], callback: (...args: any[]) => void) {
  /* ... */
}
invokeLater([1, 2], (x, y) => console.log(x + ', ' + y)); // OK
invokeLater([1, 2], (x?, y?) => console.log(x + ', ' + y)); // OK

5. 枚举 类型兼容性

  • 枚举类型与数字类型兼容
  • 并且数字类型与枚举类型兼容
  • 不同枚举类型之间是不兼容的
enum Status { Ready, Waiting };
enum Color { Red, Blue, Green };

let s = Status.Ready;
let num: number = 1

num = Status.Ready; // Ok: 枚举类型与数字类型兼容
s = 0; // Ok: 数字类型与枚举类型兼容
s = 1; // Ok: 数字类型与枚举类型兼容
s = Color.Red;  // Error: 不同枚举类型之间是不兼容的

6. 类型兼容性

  • 其兼容性与对象字面量和接口差不多
  • 不同:类有静态部分和实例部分的类型,比较两个类类型的对象时,只有实例的成员会被比较,静态成员和构造函数不在比较的范围内
class Animal {
  feet: number = 4;
  constructor(name: string, numFeet: number) { }
}
class Size {
  feet: number = 4;
  constructor(numFeet: number) { }
}
let a1: Animal = new Animal('a1', 4);
let s1: Size  = new Size(4);

a1 = s1;  // OK
s1 = a1;  // OK

7. 类 -- 私有成员和受保护成员 类型兼容性

  • 类的私有成员和受保护成员会影响兼容性
  • 这允许子类赋值给父类
class Animal2 {
  protected tag: string = 'Animal2'
  feet: number = 4;
  constructor(name: string, numFeet: number) { }
}
class Size2 {
  private tag: string = 'Size2'
  feet: number = 4;
  constructor(numFeet: number) { }
}
class Person2 {
  feet: number = 2;
  constructor(numFeet: number) { }
}
let a2: Animal2 = new Animal2('a1', 4);
let s2: Size2  = new Size2(4);
let p2: Person2 = new Person2(4)

a2 = s2  // Error
s2 = a2  // Error
a2 = p2  // Error: 类型 "Person2" 中缺少属性 "tag",但类型 "Animal2" 中需要该属性
s2 = p2  // Error: 类型 "Person2" 中缺少属性 "tag",但类型 "Size2" 中需要该属性
p2 = a2  // OK
p2 = s2  // OK

8. 泛型 类型兼容性

  • 因为TypeScript是结构性的类型系统,类型参数只影响使用其做为类型一部分的结果类型
interface Empty<T> {}
let e1: Empty<number> = { x: 1 };
let e2: Empty<string> = { x: '1' };

e1 = e2; // OK: 因为y与x的结构相匹配


interface NotEmpty<T> {
  data: T;
}
let e3: NotEmpty<number> = { data: 1 };
let e4: NotEmpty<string> = { data: '1' };;
e3 = e4; // Error: 因为x和y不兼容 , 不能将类型“string”分配给类型“number”

// 对于没指定泛型类型的泛型参数时,会把所有泛型参数当成any比较。 然后用结果类型进行比较
let identity = function<T>(x: T): T {}
let reverse = function<U>(y: U): U {}
identity = reverse;  // OK: (x: any) => any 兼容 (y: any) => any