先来看个问题
const foo = (data: {a: number, b: string}): void => console.log(data);
let data1: {a: number} = {a: 1};
let data2: {a: number, b: string, c: string[]} = {a: 1, b: 'b', c: []};
// 请说出下面4个的结果
foo(data1)
foo(data2)
data1 = data2
foo(data1)
子类型比父类型更加具体,父类型比子类型更宽泛,所以子可以兼容父
// 在集合论中,属性更少的集合是子集。
function f(val: { a: number; b: number }) {}
let val1 = { a: 1 }
let val2 = { a: 1, b: 2, c: 3 }
f(val1); // error
f(val2); // val2 是 val 的父类型
// 在类型系统中,属性更多的类型是子类型。如下面的 动物类
interface Animal {age: number}
interface Dog extends Animal {bark(): void}
let animal: Animal
let dog: Dog
animal = dog // ✅ok
dog = animal // ❌error! animal 实例上缺少属性 'bark'
wiki: 协变与逆变(Covariance and contravariance )是在计算机科学中,描述具有父/子型别关系的多个型别通过型别构造器、构造出的多个复杂型别之间是否有父/子型别关系的用语。
简单说就是,具有父子关系的多个类型,在通过某种构造关系构造成的新的类型,如果还具有父子关系则是协变的,而关系逆转了(子变父,父变子)就是逆变的。
// 基于上面的 动物类
type Covariant<T> = T[];
let coSuperType: Covariant<SuperType> = [];
let coSubType: Covariant<SubType> = [];
coSuperType = coSubType; // 新的类型依然具有父子关系
type Contravariant<T> = (p: T) => void;
let contraSuperType: Contravariant<SuperType> = function(p) {}
let contraSubType: Contravariant<SubType> = function(p) {}
contraSubType = contraSuperType; // 函数是逆变的,父子关系反转,如果集合
contraSuperType(subType);
contraSubType(superType); // error
开启了 tsconfig 中的 strictFunctionType 后才会严格按照 逆变 来约束赋值关系。
class Animal { }
class Cat extends Animal {meow() {console.log('cat meow');}}
class Dog extends Animal {wow() { console.log('dog wow'); }}
let catList: Cat[] = [new Cat()];
let animalList: Animal[] = [new Animal()];
let dog = new Dog();
animalList = catList;
animalList.push(dog);
catList.forEach(cat => cat.meow()); // cat.meow is not a function