0%

协变与逆变

协变(Covariance)

简单说,协变表示类型在某个方向上保持“越具体越好”的兼容性

在 TypeScript 中,返回值类型是协变的

1
2
3
4
5
6
7
8
type Animal = { name: string }
type Dog = { name: string; bark: () => void }

let getAnimal: () => Animal
let getDog: () => Dog

getAnimal = getDog // ✅ 合法
getDog = getAnimal // ❌ 报错

解释一下:

  • getDog 返回的是更具体的类型(Dog),赋值给返回类型是 AnimalgetAnimal 是没问题的;
  • 反过来则不行,因为 getAnimal 可能返回一只猫,你不能假设它一定会 bark()

这就是协变:返回值可以更具体,但不能更泛化

逆变(Contravariance)

逆变是参数类型的行为,和协变正好相反:越泛化越安全

1
2
3
4
5
6
7
8
type Animal = { name: string }
type Dog = { name: string; bark: () => void }

let handleAnimal: (a: Animal) => void
let handleDog: (d: Dog) => void

handleDog = handleAnimal // ✅ 合法
handleAnimal = handleDog // ❌ 报错

解释:

  • handleAnimal 能处理所有动物,当然也能处理狗。
  • handleDog 只会处理会叫的狗,万一你传进去一只猫,它不会 bark() 就炸了。

这就是逆变:参数类型越宽泛,越保险

Tips

  • 如果实在记不住,就这么记:返回值协变,参数逆变
  • TypeScript 实际上在函数参数的逆变判断上有点“宽松”(默认是双向协变),但开启 strictFunctionTypes 后才是“标准模式”
  • Array<T> 是协变的(因为只读),但 T[] 是不完全协变的(因为可以写)

最后

搞懂协变/逆变,本质是在理解“类型的替换是否安全”这个事。不是晦涩的概念,而是类型系统背后的安全保障。

如果你经常写泛型函数、封装库、或者用第三方复杂类型推导,理解它们会让你更放心地“放类型飞”。