TypeScript 中,函数类型有着协变和逆变两种关系。这两种关系是非常重要的概念,尤其是在设计接口和泛型的时候,深入理解它们可以帮助我们更好地使用 TypeScript。本章节将会介绍协变和逆变的概念,并通过代码示例来展示它们的应用。
1、协变
协变是指一个类型 S 可以被赋值给另一个类型 T,如果 S 比 T 更具体(更窄)。这个概念在函数类型中的应用,可以理解为函数参数的类型可以是参数类型的子类型。换句话说,如果我们将一个接受类型为 Animal 的函数赋值给一个接受类型为 Cat 的函数,那么就是在进行协变。
我们可以通过下面的代码示例来展示协变:
interface Animal {
name: string;
}
interface Cat extends Animal {
meow(): void;
}
function takeAnimal(animal: Animal) {
console.log(`Taking care of ${animal.name}`);
}
function takeCat(cat: Cat) {
console.log(`Taking care of ${cat.name}`);
cat.meow();
}
takeAnimal = takeCat; // 协变
在上面的代码中,我们定义了两个函数 takeAnimal 和 takeCat,它们接受不同的参数类型。takeAnimal 接受类型为 Animal 的参数,而 takeCat 接受类型为 Cat 的参数。然后,我们将 takeCat 赋值给 takeAnimal,这是一种协变的情况。因为 Cat 是 Animal 的子类型,所以接受 Cat 的函数可以被用作接受 Animal 的函数。
2、逆变
逆变是指一个类型 S 可以被赋值给另一个类型 T,如果 S 比 T 更加抽象(更宽)。在函数类型中的应用,可以理解为函数参数的类型可以是参数类型的超类型。换句话说,如果我们将一个接受类型为 Cat 的函数赋值给一个接受类型为 Animal 的函数,那么就是在进行逆变。
我们可以通过下面的代码示例来展示逆变:
interface Animal {
name: string;
}
interface Cat extends Animal {
meow(): void;
}
function takeAnimal(animal: Animal) {
console.log(`Taking care of ${animal.name}`);
}
function takeCat(cat: Cat) {
console.log(`Taking care of ${cat.name}`);
cat.meow();
}
takeCat = takeAnimal; // 逆变
在上面的代码中,我们将 takeAnimal 赋值给 takeCat,这是一种逆变的情况。因为 Animal 是 Cat 的超类型,所以接受 Animal 的函数可以被用作接受 Cat 的函数。
3、逆变和协变在泛型中的应用
逆变和协变不仅仅适用于函数类型,它们还可以应用于泛型类型。我们可以使用 in 表示逆变,使用 out 表示协变。在 TypeScript 中,逆变和协变在泛型中的应用是非常常见的,它们可以帮助我们更好地定义通用的接口和函数。
我们可以通过下面的代码示例来展示泛型中的逆变和协变:
interface Animal {
name: string;
}
interface Cat extends Animal {
meow(): void;
}
interface Box<T> {
value: T;
}
let animalBox: Box<Animal> = { value: { name: "Fluffy" } };
let catBox: Box<Cat> = { value: { name: "Whiskers", meow() {} } };
function takeAnimalBox(box: Box<Animal>) {
console.log(`Taking care of ${box.value.name}`);
}
function takeCatBox(box: Box<Cat>) {
console.log(`Taking care of ${box.value.name}`);
box.value.meow();
}
takeAnimalBox = takeCatBox; // 逆变
catBox = animalBox; // 协变
在上面的代码中,我们定义了一个泛型类型 Box
小结
在 TypeScript 中,函数类型有着协变和逆变两种关系,这两种关系是非常重要的概念。协变和逆变可以应用于函数类型和泛型类型,帮助我们更好地定义通用的接口和函数。理解协变和逆变的概念可以帮助我们更好地使用 TypeScript,并避免一些潜在的错误。