逆变与协变

2025/3/12 TypeScript

在TypeScript中,逆变(Contravariance)和协变(Covariance)是描述类型系统中子类型关系的概念,主要应用于函数参数和返回值的类型兼容性。

# 基本概念

# 子类型与超类型

  • 子类型(Subtype):如果类型A是类型B的子类型,则A可以赋值给B
  • 超类型(Supertype):如果类型B是类型A的超类型,则B可以接受A的赋值。

例如:

type Animal = { name: string };
type Dog = { name: string; breed: string };

let animal: Animal;
let dog: Dog = { name: "Buddy", breed: "Golden Retriever" };

animal = dog; // Dog 是 Animal 的子类型
1
2
3
4
5
6
7

# 协变(Covariance)

协变是指子类型关系在复合类型中保持方向一致。例如,如果DogAnimal的子类型,那么Array<Dog>也是Array<Animal>的子类型。

type Animal = { name: string };
type Dog = { name: string; breed: string };

let animals: Animal[];
let dogs: Dog[] = [{ name: "Buddy", breed: "Golden Retriever" }];

animals = dogs; // 协变:Dog[] 是 Animal[] 的子类型
1
2
3
4
5
6
7

应用场景

  • 返回值类型:函数的返回值类型是协变的。
type Animal = { name: string };
type Dog = { name: string; breed: string };

let getAnimal: () => Animal;
let getDog: () => Dog = () => ({ name: "Buddy", breed: "Golden Retriever" });

getAnimal = getDog; // 协变:getDog 可以赋值给 getAnimal
1
2
3
4
5
6
7

# 逆变(Contravariance)

逆变是指子类型关系在复合类型中方向相反。例如,如果DogAnimal的子类型,那么(param: Animal) => void(param: Dog) => void的子类型。

示例

type Animal = { name: string };
type Dog = { name: string; breed: string };

const handleAnimal: (animal: Animal) => void = (animal) => console.log(animal.name);
let handleDog: (dog: Dog) => void = (dog) => console.log(dog.breed);

handleDog = handleAnimal; // 逆变:handleAnimal 可以赋值给 handleDog
1
2
3
4
5
6
7

应用场景

  • 参数类型:函数的参数类型是逆变的。

# 双变(Bivariance)

双变是指类型关系既可以是协变的,也可以是逆变的。TypeScript在函数参数类型中默认支持双变(出于兼容性考虑),但可以通过配置strictFunctionTypes来禁用双变。

示例

type Animal = { name: string };
type Dog = { name: string; breed: string };

let handleAnimal: (animal: Animal) => void;
let handleDog: (dog: Dog) => void = (dog) => console.log(dog.breed);

handleAnimal = handleDog; // 双变:handleDog 可以赋值给 handleAnimal
handleDog = handleAnimal; // 双变:handleAnimal 可以赋值给 handleDog
1
2
3
4
5
6
7
8

总结

特性 协变(Covariance) 逆变(Contravariance) 双变(Bivariance)
定义 子类型关系方向一致 子类型关系方向相反 子类型关系既可以是协变也可以是逆变
应用场景 返回值类型、数组类型 参数类型 TypeScript函数参数默认行为
示例 Dog[]Animal[]的子类型 (Animal) => void(Dog) => void的子类型 函数参数类型默认支持双变

配置严格模式

在TypeScript中,可以通过tsconfig.json中的strictFunctionTypes选项来启用严格的函数类型检查,禁用双变行为:

{
  "compilerOptions": {
    "strictFunctionTypes": true
  }
}
1
2
3
4
5

启用后,函数参数类型将只支持逆变,不符合逆变规则的赋值会报错。

最近更新: 2025年03月13日 17:49:47