逆变、协变、双向协变、不变

类型安全和型变

我们都知道在ts中,我们可以给变量定义为不同的类型,例如number类型和string类型。但是,除了这种死板的类型限制,还有一些灵活的类型限制。比如子类型是可以赋值给父类型的。这就是型变(类型改变)。这种型变分为两种。一种是子类型赋值给父类型叫做协变。一种是父类型赋值给子类型叫做逆变

协变

interface Person{
  name: string,
  age: number
}
interface Student{
  name: string,
  age: number,
  certificate: number
}
let person:Person = {
  name: 'name',
  age: 1
}
let student:Student = {
  name: 'name2',
  age: 2,
  certificate: 2
}
person = student;

逆变

interface Person{
  name: string,
  age: number
}
interface Student{
  name: string,
  age: number,
  certificate: number
}
let printHobbies:(student: Student)=>void;
let printName:(person: Person)=>void;
printHobbies = (student)=>{
  console.log(student);
}
printName = (person)=>{
  console.log(person);
}
printHobbies = printName;
// printName = printHobbies;//不能将类型“(student: Student) => void”分配给类型“(person: Person) => void”。

为什么父类型可以赋值给子类型呢?因为这个函数用的是Student来约束类型的,但实际上函数如果只用了父类型Person的属性和方法,也不会有问题,依然是安全的类型。函数的参数是逆变,返回值是协变。

type Func = (a: string) => void;
const func: Func = (a: 'hello') => undefined//报错
//因为string不是'hello'的子类型,所以报错
type Func = (a: string) => void;
const func: Func = (a: string) => undefined
//因为undefined是void的子类型,所以不报错

双向协变

在ts2.x之前支持这种赋值,也就是父类型可以赋值给子类型,子类型也可以赋值给父类型。既逆变又协变。叫做双向协变双向协变不能保证类型安全,所以,之后ts加了一个编译选项,strictFunctionTypes,设置为true就支持函数的逆变,设置为false就支持双向协变。

不变

非父子类型之间不会发生型变,只要类型不一样就会报错。

类型父子关系的判断

在java中,如果A extends B,那么A就是B的子类型。(名义系统类型)在ts中,只要结构上是一致的,那么就可以确定父子关系。(结构类型系统)

参考资料

[1]TypeScript 类型体操通关秘籍