返回

漫谈TypeScript之类型兼容与协变、逆变、双向协变

前端

TypeScript中的类型兼容性

TypeScript中的类型兼容性是基于结构类型的,这意味着两个类型是否兼容取决于它们的组成结构是否相同。这种兼容性检查方式也称为“鸭式辨型法”,它关注的是对象的实际行为,而不是它们的类型名称。

例如,以下代码中的两个类型PersonEmployee是兼容的,因为它们具有相同的结构:

interface Person {
  name: string;
  age: number;
}

interface Employee {
  name: string;
  age: number;
  salary: number;
}

const person: Person = {
  name: "John",
  age: 30
};

const employee: Employee = {
  name: "Jane",
  age: 35,
  salary: 100000
};

// 因为Person和Employee是兼容的,所以可以将employee赋值给person变量
person = employee;

协变、逆变和双向协变

协变、逆变和双向协变是TypeScript中类型之间关系的三种方式。

  • 协变 :协变类型允许子类型的值可以赋值给父类型。例如,如果Animal是一个父类,Dog是一个子类,那么Dog类型的变量可以赋值给Animal类型的变量。

  • 逆变 :逆变类型允许父类型的值可以赋值给子类型。例如,如果Animal是一个父类,Dog是一个子类,那么Animal类型的变量可以赋值给Dog类型的变量。

  • 双向协变 :双向协变类型允许子类型的值可以赋值给父类型,父类型的值也可以赋值给子类型。例如,如果Animal是一个父类,Dog是一个子类,那么Dog类型的变量可以赋值给Animal类型的变量,Animal类型的变量也可以赋值给Dog类型的变量。

协变、逆变和双向协变的应用

协变、逆变和双向协变在TypeScript中有很多应用场景,例如:

  • 函数参数和返回值的协变 :函数的参数和返回值可以是协变的,这可以使函数更加灵活。例如,以下代码中的函数max可以接受任何实现了Comparable接口的类型作为参数,并返回最大值:
interface Comparable {
  compareTo(other: Comparable): number;
}

function max<T extends Comparable>(a: T, b: T): T {
  if (a.compareTo(b) > 0) {
    return a;
  } else {
    return b;
  }
}

const x: number = max(1, 2); // 可以正常调用,因为number实现了Comparable接口
const y: string = max("hello", "world"); // 也可以正常调用,因为string也实现了Comparable接口
  • 数组的协变 :数组的类型可以是协变的,这可以使数组更加灵活。例如,以下代码中的数组numbers可以存储任何类型的数字:
const numbers: Array<number> = [1, 2, 3];

numbers.push(4.5); // 可以正常添加,因为4.5也是数字

const value = numbers[0]; // value的类型为number,因为数组的类型是协变的
  • 泛型的协变和逆变 :泛型的类型参数可以是协变的或逆变的,这可以使泛型更加灵活。例如,以下代码中的泛型类Stack可以存储任何类型的元素:
class Stack<T> {
  private items: T[] = [];

  push(item: T) {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }
}

const stack = new Stack<number>();

stack.push(1);
stack.push(2);
stack.push(3);

const value = stack.pop(); // value的类型为number,因为泛型类Stack的类型参数T是协变的

总结

协变、逆变和双向协变是TypeScript中重要的概念,理解这些概念有助于您编写出更健壮、更灵活的代码。希望本文对您有所帮助。