typescript 协变与逆变的理解

663 阅读3分钟

有时候在处理父子类型赋值时会出现类型错误, 这时可能就涉及到协变 与 逆变 , 这里记录个人的理解。

协变例子

image.png

逆变例子

image.png

类型范围

在理解协变与逆变前我们需要先了解类型的范围, 父子类型的类型范围是, 子类型范围 > 父类型范围。 这里很好理解,子类型都是父类型的扩展,自然子类型可能包含更广或更具体的类型定义。

image.png

类型安全

类型做赋值时,如何保证我所赋值的类型安全呢? 自然是类型范围小的可以接收范围大的类型值,反之则是不安全的。 回到上面协变的例子, _p 类型定义为父类, 接收的是子类型的值。 使用 _p 时只会用到父类类型范围内的定义, 这里子类所包含的, 自然是类型安全的,反之 _c 类型定义为子类, 如果接收父类作为值, 如果使用到子类类型范围内的定义, 例如: _c.job, 这时父类所不具备的,自然是不安全的。 符合这个规则的就是协变 , 从设计原则的角度看就是要符合类型的里氏替换。

协变与逆变

大概了解了协变后,如何理解逆变呢? 直观的感受就是逆变就是协变的反向。 但是仔细分析后,会发现协变与逆变指的是同一个概念,只是针对的对象不同而已。

  • 逆变针对的是函数的参数值
  • 协变针对的是除去逆变的其他情况
  • 两者都是限制正对的对象赋值时,小范围的类型定义可接收大范围的定义定义, 反之不可行。

为什么这么说呢? 回到逆变的例子, 如果我们将 _c 的类型定义范围看作参数c 的类型范围, _p 的类型定义范围看作参数p 的类型返回, 是不是就与协变的类型返回规则一致了呢! _c 根据类型定义,接收child 类型的参数,如果_c 保存的值是函数p , 而p 可接收的参数类型为 parent , 在_c 参数类型的定义范围内, 所以是类型安全的。反之就是不安全的。 // 假设没有协变限制 const _p: PFn = c const pAge:Parent = { name: 'xx' } // 调用_p, 当前 _p 保存的类型 CFn 的函数 _p(pAge) // 调用将报错,因为pAge上不存在属性 job

总结

自己对协变与逆变的理解经常容易混淆,如果只记住逆变是协变的反向的话。在实际分析类型定义时,经常会出现逻辑混乱的问题,现在基本记住 1. 大范围的值可以赋值给小范围的值, 2. 函数需要参考参数的定义范围。 以上都是个人的一些理解,希望下次忘记时,会看能帮助自己快速理解协变与逆变的概念