个人复习用,不做科普。

创建两个类,DogAnimal的子类。

class Animal {}

class Dog extends Animal {
    bark() {}
}

实现一个判断类型的Type,判断T是否是U的子类。

type Assignable<T, U> = T extends U ? true : false

测试,符合结果。

type R4 = Assignable<Dog, Animal> // true

但将同样的问题放到函数的parameters上,就发生了与第一直觉不一样的情况。


type ConsumeAnimal = (target: Animal) => Animal
type ConsumeDog = (target: Dog) => Dog

type R1 = Assignable<Son, Father> // false
type R2 = Assignable<Father, Son> // true

type R3 = Assignable<Parameters<Son>, Parameters<Father>> // true

问题出在哪里?

通过Google,找到两个关键词:covariancecontravariance

  1. 什么是covariance?

Covariance accept subtype but doesn’t accept supertype

animal = dog, animal 可以接受一个子类 dog.

  1. 什么是contravariance?

Contravariance accept supertype but doesn’t accept subtype

contravariance

将函数ConsumeAnimal看作为Consumer,它消费一个类Animal,保证参数target: Animal存在Animal的所有属性和方法,该函数绝对访问不会超过Animal之外的属性。 而ConsumeDog可能会访问到Animal的属性,但也可能访问到Dog的属性,这时候已经超过了ConsumeAnimal的范围,因为它拿到的只能是一个Animal!

而反过来看,ConsumeDog消费一个Dog类,保证参数target: Dog存在Dog的所有属性和方法。 如果ConsumeAnimal所做的行为,同样可以应用到类型Dog身上,因为Dog类包含Animal的所有属性和方法。

用以下代码解释上面的问题:

type ConsumeAnimal = (target: Animal) => Animal
type ConsumeDog = (target: Dog) => Dog

declare const consumeAnimal: ConsumeAnimal
declare const consumeDog: ConsumeDog

consumeAnimal = consumeDog
^^^^^^^^^^^^
// Property 'bark' is missing in type 'Animal' but required in type 'Dog'

consumeAnimal函数能传入AnimalDog,因为Dog也能提供所有Animal的属性。
而将consumeDog赋值给consumeAnimal
当向其传入Animal时,consumeDog有可能会访问Animal上所不存在的属性bark,超过了Animal的范围。

而以下代码就没有问题

declare const consumeAnimal: ConsumeAnimal
declare const consumeDog: ConsumeDog
consumeDog = consumeAnimal

ConsumeDog可以传入Dog,但不可以传入Animal,因为Animal不能保证提供所有Dog的属性。
所以实际的消费函数consumeAnimal可以非常安全的消费一只Dog,所以consumeAnimal可以赋值给consumeDog

covariance

但对于对象实例来讲,它们可以作为一个Provider。和以上函数的情况是相反的。

declare let animal: Animal
declare let dog: Dog
animal = dog
dog = animal
^^^
// Property 'bark' is missing in type 'Animal' but required in type 'Dog'

因为dog属于animal的子类,所以dog可以很安全的赋值给一个animal类型。因为dog能过提供所有animal的属性。 反之,animal不能安全的赋值给dog,它可能不包含某些dog的属性。

总结

上面一堆绕话,是为了理清思路。这里尝试总结一下,供以后回顾。

A ≼ B表示A是B的子类
T → U表示一个函数参数类型为T,返回类型为U
那那:

  • 对于类「实例」A ≼ B,是covariance。
  • 对于函数「返回值」来讲 (T → A) ≼ (T → B),是covariance。
  • 对于函数「参数」 (B → T) ≼ (A → T),是contravariance。

其它

还有关于一个List<T>invariant情况(该作者的解释简单明了,所以直接引用过来了):

Could List<Dog> be a subtype of List<Animal>?

The answer is a little nuanced. If lists are immutable, then it’s safe to say yes. But if lists are mutable, then definitely not!

Why? Suppose I need a List<Animal> and you pass me a List<Dog>. Since I think I have a List<Animal>, I might try to insert a Cat into it. Now your List<Dog> has a Cat in it! The type system should not allow this. Formally: we can allow the type of immutable lists to be covariant in its type parameter, but the type of mutable lists must be invariant (neither covariant nor contravariant) in its type parameter.

参考资料

https://www.stephanboyer.com/post/132/what-are-covariance-and-contravariance https://dmitripavlutin.com/typescript-covariance-contravariance/ https://dev.to/codeoz/how-i-understand-covariance-contravariance-in-typescript-2766