返回

深入剖析 TypeScript 类型兼容性:逆变、协变、双向协变和不变

前端

在 TypeScript 的世界中,理解类型兼容性至关重要,它决定了代码在运行时的行为以及重构的可能性。本文深入探讨了 TypeScript 中四种类型的兼容性:逆变、协变、双向协变和不变。

类型兼容性简介

在 TypeScript 的基于名义的类型系统中,类型兼容性通过显式声明或类型名称来确定。当一个类型与另一个类型兼容时,我们可以将该类型的值安全地赋值给另一个类型的值。

逆变(Contravariance)

逆变性适用于函数或回调的参数类型。逆变函数类型接收比预期更宽松类型的参数。换句话说,如果函数类型 A 接受参数类型 B,并且 B 是类型 C 的子类型,那么函数类型 A 也可以接受参数类型 C。

// 定义一个逆变函数类型
interface Printable<T> {
  print(value: T): void;
}

// 可以将 `Printable<number>` 赋值给 `Printable<any>`
let printableNumber: Printable<number> = (value) => console.log(value);
let printableAny: Printable<any> = printableNumber;

协变(Covariance)

协变性适用于函数或回调的返回类型。协变函数类型返回比预期更严格类型的返回值。换句话说,如果函数类型 A 返回类型 B,并且 B 是类型 C 的父类型,那么函数类型 A 也可以返回类型 C。

// 定义一个协变函数类型
interface Gettable<T> {
  get(): T;
}

// 可以将 `Gettable<any>` 赋值给 `Gettable<number>`
let gettableAny: Gettable<any> = { get: () => 123 };
let gettableNumber: Gettable<number> = gettableAny;

双向协变(Bivariance)

双向协变性同时适用于函数或回调的参数类型和返回类型。双向协变函数类型接受比预期更宽松类型的参数,并返回比预期更严格类型的返回值。

// 定义一个双向协变函数类型
interface Comparable<T> {
  compareTo(other: T): number;
}

// 可以将 `Comparable<number>` 赋值给 `Comparable<any>`
let comparableNumber: Comparable<number> = (a, b) => a - b;
let comparableAny: Comparable<any> = comparableNumber;

不变(Invariance)

不变性是默认的类型兼容性行为。不变函数类型既不接受比预期更宽松类型的参数,也不返回比预期更严格类型的返回值。

// 定义一个不变函数类型
interface Identifiable {
  getId(): string;
}

// 不能将 `Identifiable<number>` 赋值给 `Identifiable<any>`
let identifiableNumber: Identifiable<number> = { getId: () => '123' };
//let identifiableAny: Identifiable<any> = identifiableNumber; // 类型不兼容

应用

理解 TypeScript 中的类型兼容性对于编写健壮、灵活的代码至关重要。例如,逆变函数类型可以轻松处理不同类型的输入,而协变函数类型可以方便地处理继承层次结构中的返回值。

结论

TypeScript 中的逆变、协变、双向协变和不变性是强大的工具,它们可以提高代码的可读性、可维护性和可扩展性。通过了解这些概念及其应用,开发者可以编写出更健壮、更灵活的 TypeScript 代码。