逆变和协变
定义
协变:类型推导到其子类型的过程,A | B -> A & B 就是一个协变
逆变:类型推导到其超类型的过程
如何判断函数子类型?
为了方便描述,「Dog => Dog」 表示「参数为 Dog,返回值为 Dog 的函数」
其他类型的子类型我们很好判断,函数的子类型却很难,比如有关系 Animal -> Dog -> Shepherd
Animal => Shepherd 是 Dog => Dog 子类型吗?
是的
我们如果把视角划分,Dog => Dog 作为
参数的传入者:只能保证传入 Dog 参数,所以当我们定义参数为 Animal 时,只能使用 Animal 上的属性和方法,而 Dog 肯定有,就能保证类型的正确。
使用返回值者:保证只使用 Dog 方法,所以当我们定义返回值为 Shepherd,使用者只使用 Dog 上的属性和方法,而 Shepherd 肯定有,就能保证类型的正确。
所以,Dog => Dog -> Animal => Shepherd
也称 参数是逆变的,返回值是协变的。
至于你为什么这么少看到协变和逆变的概念,只因为 TypeScript 只有一处逆变,就是参数
举例:
协变
Covariance
如果T ≤ U,那么F<T> ≤ F<U>也成立,这就叫协变。很容易理解吧。
这里协变主要讲函数的返回值类型的检查。
type Co<V> = () => V;
// Co<Dog> ≤ Co<Animal>
const animalFn: Co<Animal> = () => {
return new Animal();
}
const dogFn: Co<Dog> = () => {
return new Dog();
}
let a: Co<Animal> = dogFn; // ok,dogFn返回Dog,Dog本身就是Animal
let b: Co<Dog> = animalFn; // error,animalFn返回Animal,Animal不一定是Dog,有可能不会doDogThing
可以看到,函数的返回值类型要协变才安全,否则ts可能会报错。
逆变
Cotravariance
跟协变相反,如果T ≤ U,那么F<U> ≤ F<T>成立,这就叫逆变。
这里逆变主要讲的是函数的参数类型的检查。
注意,是函数赋值时对参数的检查,并不是参数赋值时的检查。
当开启了--strictFunctionTypes或者--strict模式,ts才对函数参数类型进行逆变检查。
type Cotra<V> = (input: V) => void;
// Cotra<Animal> ≤ Cotra<Dog>
const animalFn: Cotra<Animal> = (input) => {
input.doAnimalThing();
}
const dogFn: Cotra<Dog> = (input) => {
input.doDogThing();
}
let a: Cotra<Animal> = dogFn; // error,Animal没有doDogThing方法
let b: Cotra<Dog> = animalFn; // ok
这里可能有点难理解,但是细想一下,就会发现这是合理的。
方法a我们定义入参为一个Animal,但是赋值是dogFn,调用方法a时如果真的传入Animal,由于Animal没有doDogThing方法,一定会执行出错。所以这里ts会提示错误。
但反过来就没问题。方法b传入Dog,Dog继承Animal,是有doAnimalThing方法的。