类型兼容性规则
TypeScript的类型兼容性规则是类型系统中一个核心概念,决定了不同类型之间是否可以相互赋值或替换。它基于结构化类型而非名义类型,这使得类型检查更灵活,但也可能带来一些意料之外的行为。
类型兼容性的基本原则
TypeScript采用结构化类型系统,即只要两个类型的结构相同,它们就是兼容的。这与名义类型系统(如Java)形成鲜明对比。例如:
interface Person {
name: string;
age: number;
}
class Employee {
name: string;
age: number;
salary: number;
}
const person: Person = new Employee(); // 兼容,因为Employee包含Person的所有属性
这里Employee
类包含了Person
接口的所有属性,因此可以将Employee
实例赋值给Person
类型的变量。
函数类型兼容性
函数类型的兼容性规则更为复杂,需要考虑参数类型和返回值类型。
参数类型兼容性
函数参数采用"双向协变"规则:目标函数的参数类型必须是源函数参数类型的子类型或相同类型。
type Handler = (event: Event) => void;
const clickHandler: Handler = (e: MouseEvent) => console.log(e.button); // 兼容
虽然MouseEvent
是Event
的子类型,但TypeScript允许这种赋值,这被称为"参数双向协变"。
返回值类型兼容性
返回值类型采用"协变"规则:目标函数的返回值类型必须是源函数返回值类型的子类型或相同类型。
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
function getAnimal(): Animal {
return { name: "Animal" };
}
function getDog(): Dog {
return { name: "Dog", breed: "Labrador" };
}
const animalFunc: () => Animal = getDog; // 兼容
const dogFunc: () => Dog = getAnimal; // 不兼容
对象类型兼容性
对象类型的兼容性检查是递归进行的,要求目标类型的所有属性在源类型中都存在且类型兼容。
多余属性检查
当直接赋值对象字面量时,TypeScript会进行额外检查:
interface Point {
x: number;
y: number;
}
const p: Point = { x: 1, y: 2, z: 3 }; // 错误:对象字面量只能指定已知属性
可以通过类型断言或中间变量绕过这个检查:
const p1: Point = { x: 1, y: 2, z: 3 } as Point; // 类型断言
const temp = { x: 1, y: 2, z: 3 };
const p2: Point = temp; // 通过中间变量
类类型兼容性
类的兼容性与对象类似,但需要注意静态成员和私有成员:
class A {
private secret: string;
constructor(public name: string) {}
}
class B {
private secret: string;
constructor(public name: string) {}
}
const a: A = new B("test"); // 错误:私有属性不兼容
即使两个类的结构相同,只要它们有私有或受保护成员,且这些成员来自不同的声明,它们就不兼容。
泛型类型兼容性
泛型类型的兼容性取决于具体类型参数的兼容性:
interface Empty<T> {}
let x: Empty<number>;
let y: Empty<string> = x; // 兼容,因为Empty<T>没有使用T
interface NotEmpty<T> {
data: T;
}
let a: NotEmpty<number>;
let b: NotEmpty<string> = a; // 不兼容
枚举类型兼容性
枚举类型与数字类型兼容,但不同枚举类型之间不兼容:
enum Status { Ready, Waiting }
enum Color { Red, Blue, Green }
let s = Status.Ready;
s = 1; // 兼容
s = Color.Red; // 不兼容
高级类型兼容性
联合类型
联合类型的兼容性检查需要考虑所有可能的类型:
type U = string | number;
let u: U = "hello"; // 兼容
u = 42; // 兼容
u = true; // 不兼容
交叉类型
交叉类型的兼容性要求满足所有组成类型:
interface A {
a: number;
}
interface B {
b: string;
}
type C = A & B;
const c: C = { a: 1, b: "test" }; // 兼容
const c2: C = { a: 1 }; // 不兼容,缺少b属性
类型参数兼容性
对于泛型函数,类型参数会影响兼容性:
let identity = function<T>(x: T): T {
return x;
};
let reverse = function<U>(y: U): U {
return y;
};
identity = reverse; // 兼容,因为类型参数只在内部使用
可选参数和剩余参数
函数中的可选参数和剩余参数也影响兼容性:
function foo(x: number, y?: number) {}
function bar(x: number) {}
function baz(x: number, ...rest: number[]) {}
foo = bar; // 兼容
bar = foo; // 兼容
baz = foo; // 兼容
foo = baz; // 兼容
函数重载兼容性
对于重载函数,源函数的每个重载签名都必须能在目标函数中找到兼容的签名:
function overload(a: number): number;
function overload(a: string): string;
function overload(a: any): any {
return a;
}
function simple(a: string | number): string | number {
return a;
}
overload = simple; // 不兼容
simple = overload; // 兼容
索引签名兼容性
对象类型的索引签名会影响兼容性:
interface StringArray {
[index: number]: string;
}
let strArr: StringArray = ["a", "b"]; // 兼容
strArr = { 0: "a", 1: "b" }; // 兼容
strArr = { 0: "a", 1: 42 }; // 不兼容
类型推断与兼容性
TypeScript的类型推断会影响兼容性判断:
let x = [0, 1, null]; // 推断为(number | null)[]
const y: number[] = x; // 不兼容
上下文类型
上下文类型会影响函数表达式的兼容性:
window.onmousedown = function(mouseEvent) {
console.log(mouseEvent.button); // 兼容,mouseEvent被推断为MouseEvent
};
严格函数类型检查
启用--strictFunctionTypes
后,函数参数类型检查会更严格:
type Methodish = {
func(x: string | number): void;
};
const m: Methodish = {
func: (x: string) => console.log(x) // 不兼容(严格模式下)
};
类型保护与兼容性
类型保护会影响类型兼容性判断:
function isString(x: any): x is string {
return typeof x === "string";
}
function example(x: string | number) {
if (isString(x)) {
const s: string = x; // 兼容
}
}
映射类型兼容性
映射类型会保留原始类型的兼容性关系:
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface Person {
name: string;
age: number;
}
const readonlyPerson: Readonly<Person> = { name: "Alice", age: 30 };
const person: Person = readonlyPerson; // 不兼容(因为属性是只读的)
条件类型兼容性
条件类型的兼容性取决于最终解析的类型:
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
"object";
type T1 = TypeName<string>; // "string"
type T2 = TypeName<number>; // "number"
const t1: T1 = "string"; // 兼容
const t2: T2 = "number"; // 兼容
const t3: T1 = "number"; // 不兼容
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:枚举类型的使用与限制
下一篇:类与继承语法