typescript 逆变协变的问题

一直对typescript的逆变,协变和双向协变似懂非懂,代码来自官方文档

function combine<T>(...funcs: ((x: T) => void)[]): (x: T) => void {
  return x => {
    for (const f of funcs) f(x);
  };
}
function animalFunc(x: Animal) {}
function dogFunc(x: Dog) {}
let combined = combine(animalFunc, dogFunc); // (x: Dog) => void

这段代码Animal是父类包含Dog,根据参数逆变应该是用范围更广的Animal代替Dog做兼容,为什么最后推断的类型却是(x: Dog) => void而不是(x: Animal) => void,
文档是这样解释:
Above, all inferences for T originate in contravariant positions, and we therefore infer the best common subtype for T. This contrasts with inferences from covariant positions, where we infer the best common supertype.
这句话我看不懂,什么是逆变的位置,什么是协变的位置,来什么T的推断来源于逆变的位置,为什么逆变的位置推断出来最好的T的公共子类型是Dog,Dog不是不能兼容Animal吗。
希望大佬们能帮我解决这个困惑,谢谢!

阅读 1.5k
1 个回答

combine函数作用就是让传入的参数能针对一个参数同时执行。animalFunc和dogFunc能同时针对什么类型的参数执行呢?

Dog继承于Animal,即Animal有的字段Dog必有

class Animal {
  liveType: '陆生'|'水生',
  color: string,
}

class Dog extends Animal {
  shape: '大型犬'|'中型犬'|'小型犬'
}

那么animalFunc只可能访问liveType和color两个属性,这两个属性对于Animal和Dog都是有的。但dogFunc除了可能访问liveType和color两个属性,还有可能访问shape属性。那么如果combined推断是(x: Animal) => void类型的,且传入的确实就是Animal类型参数(不包含shape字段),那么dogFunc执行的时候就会读到undefined。

在扩展一下,假设有个Puppy类型,继承于Dog。有一个puppyFunc: (x: Puppy) => void。那么combine(animalFunc, dogFunc, puppyFunc)应该推断出(x: Puppy) => void。Animal、Dog和Puppy求并集得到是Puppy。为什么说是并集,传入combine的n个函数,都要访问T类型的全部或部分,即animalFunc,dogFunc和puppyFunc的参数的并集,才能保证三个函数要访问的东西都有。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题