Skip to content

理解 TypeScript 中的协变、逆变、双变和不变

Posted on:2024年6月14日 at 10:41

在TypeScript中,协变、逆变、双变和不变是描述泛型类型在其类型参数上的可赋值性关系的概念。本文将通过具体的例子来解释这些概念,帮助你更好地理解它们在实际编程中的应用。

什么是协变?

协变(Covariance)指的是一个泛型类型在其类型参数上的可赋值性方向与该类型参数一致。换句话说,如果类型TU的子类型,那么F<T>也是F<U>的子类型。一个常见的例子是数组类型:

type Person = { name: string };
type Student = { name: string; graduationYear: number };

declare let students: Student[];
declare function printNames(persons: Person[]): void;

printNames(students); // 没有错误

在这个例子中,StudentPerson的子类型,所以Student[]Person[]的子类型。这就是协变。

什么是逆变?

逆变(Contravariance)指的是一个泛型类型在其类型参数上的可赋值性方向与该类型参数相反。也就是说,如果类型TU的子类型,那么F<U>F<T>的子类型。函数类型在其参数类型上是逆变的:

type Person = { name: string };
type Student = { name: string; graduationYear: number };

declare function printName(person: Person): void;
declare function forEachStudent(callback: (student: Student) => void): void;

forEachStudent(printName); // 没有错误

在这个例子中,我们可以将接受Person类型参数的函数printName传递给期望接受Student类型参数的函数forEachStudent,这是因为函数类型在其参数类型上是逆变的。

什么是不变?

不变(Invariance)指的是一个泛型类型在其类型参数上的可赋值性方向既不是协变的,也不是逆变的。如果需要一个F<T>类型的值,那么提供任何其他类型都会导致编译错误:

type In<V> = (v: V) => V;
function invariance<U, T extends U>(t: T, u: U, inT: In<T>, inU: In<U>) {
  u = t; // 可以
  t = u; // 错误!

  inU = inT; // 错误!
  inT = inU; // 错误!
}

在这个例子中,In<V>类型既不是协变的也不是逆变的,因此是不可变的。

什么是双变?

双变(Bivariance)指的是一个泛型类型在其类型参数上的可赋值性方向既是协变的也是逆变的。在一个健全的类型系统中,这种情况几乎不会发生:

type Bivariant<V> = (v: V) => void | V;
function bivariance<U, T extends U>(t: T, u: U, biT: Bivariant<T>, biU: Bivariant<U>) {
  u = t; // 可以
  t = u; // 错误!

  biU = biT; // 可以
  biT = biU; // 可以
}