TypeScript 类型兼容性是指判断一个类型是否可以被另一个类型替代,换句话说,就是一个类型的值是否可以赋值给另一个类型的变量。TypeScript 使用结构化类型系统(也称为鸭子类型或子结构类型),基于成员的实际结构而不是名称来判断类型兼容性。
1. 基本规则
在 TypeScript 中,一个类型 S
可以赋值给另一个类型 T
,当且仅当 T
兼容 S
。类型兼容性主要取决于两个方面:
- 结构兼容性:即一个类型的属性集合是否是另一个类型属性集合的子集。
- 可分配性:即一个类型的值是否可以赋值给另一个类型。
2. 接口兼容性
当判断两个接口类型的兼容性时,TypeScript 会检查一个接口的属性是否可以包含在另一个接口中。
interface Person {
name: string;
age: number;
}
interface Employee {
name: string;
age: number;
position: string;
}
let p: Person;
let e: Employee = { name: "John", age: 30, position: "Developer" };
p = e; // OK
// e = p; // Error: Property 'position' is missing in type 'Person' but required in type 'Employee'
在这个例子中,Employee
包含 Person
所有的属性,因此 Employee
类型可以赋值给 Person
类型。
3. 类兼容性
类的兼容性与接口类似,但还会考虑到类的静态成员和构造函数。只考虑实例成员和方法。
class Animal {
name: string;
}
class Dog extends Animal {
breed: string;
}
let a: Animal;
let d: Dog = { name: "Buddy", breed: "Golden Retriever" };
a = d; // OK
// d = a; // Error: Property 'breed' is missing in type 'Animal' but required in type 'Dog'
4. 函数兼容性
函数的兼容性检查主要涉及参数和返回值的类型。在检查函数兼容性时,TypeScript 会执行双向检查。
- 参数:参数类型必须兼容(逆变)。
- 返回值:返回值类型必须兼容(协变)。
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
y = x; // OK
// x = y; // Error: Cannot assign 'y' to 'x' because 'y' has more parameters than 'x'
let a = () => ({ name: "Alice" });
let b = () => ({ name: "Alice", location: "Seattle" });
a = b; // OK
// b = a; // Error: 'name' is missing in type returned by 'a' but required in type returned by 'b'
5. 泛型兼容性
泛型类型的兼容性与普通类型相似,TypeScript 会检查实际使用时泛型参数的兼容性。
interface Empty<T> {
}
let x: Empty<number>;
let y: Empty<string>;
x = y; // OK, 因为 Empty<T> 结构相同
interface NotEmpty<T> {
data: T;
}
let a: NotEmpty<number>;
let b: NotEmpty<string>;
// a = b; // Error: Property 'data' is incompatible
6. 枚举兼容性
不同枚举类型之间不兼容,但可以将枚举类型与数字类型相互赋值。
enum Status {
Ready,
Waiting
}
enum Color {
Red,
Blue,
Green
}
let s: Status = Status.Ready;
s = 1; // OK
// let c: Color = Status.Ready; // Error: Type 'Status' is not assignable to type 'Color'
7. 高级类型兼容性
联合类型和交叉类型
- 联合类型(Union Types):A | B 表示 A 或 B 的类型,可以兼容 A 或 B 的类型。
- 交叉类型(Intersection Types):A & B 表示同时是 A 和 B 的类型。
interface Bird {
fly(): void;
layEggs(): void;
}
interface Fish {
swim(): void;
layEggs(): void;
}
type Pet = Bird | Fish;
let pet: Pet;
let bird: Bird = { fly: () => {}, layEggs: () => {} };
let fish: Fish = { swim: () => {}, layEggs: () => {} };
pet = bird; // OK
pet = fish; // OK
type AquaticPet = Bird & Fish;
// let aquaticPet: AquaticPet = { fly: () => {}, swim: () => {}, layEggs: () => {} }; // OK
// let birdOnly: AquaticPet = { fly: () => {}, layEggs: () => {} }; // Error: Property 'swim' is missing
// let fishOnly: AquaticPet = { swim: () => {}, layEggs: () => {} }; // Error: Property 'fly' is missing
8. TypeScript 中类型兼容性总结
- 结构化类型系统:基于成员结构判断类型兼容性。
- 接口和类:成员类型兼容即可赋值,类需注意静态成员和构造函数。
- 函数:参数类型逆变,返回值类型协变。
- 泛型:实际使用时泛型参数需兼容。
- 枚举:与数字类型互相兼容,不同枚举类型不兼容。
- 联合类型和交叉类型:分别表示可选类型和组合类型。
通过理解和掌握 TypeScript 的类型兼容性机制,可以编写出更安全、可维护的代码,同时也可以更灵活地利用类型系统的强大功能。