核心原则:结构化类型(Duck Typing)
在 TypeScript 中,只要一个类型的结构(即它的成员)兼容,就可以赋值,不管它叫什么名字或从哪里来。
ts兼容的魅力所在:TypeScript 看"结构"不看"出身"。只要你的对象有我需要的属性,你就能当我的变量,哪怕你还会更多技能!
ts
interface Point {
x: number;
y: number;
}
class SomePoint {
x: number = 0;
y: number = 0;
}
const p: Point = new SomePoint(); // ✅ 兼容!结构相同
主要的类型兼容性规则
1. 对象类型兼容性:目标类型(Target) vs 源类型(Source)
当把一个对象赋值给另一个对象类型时,TS 检查:
源类型必须包含目标类型的所有成员(可以更多,但不能少)。 成员的类型也必须兼容。
ts
interface Named {
name: string;
}
interface Person {
name: string;
age: number;
}
const person: Person = { name: "Alice", age: 25 };
console.log(person);
const named: Named = person; // ✅ 兼容!Person 有 name 属性
console.log(named);
2. 函数类型兼容性
函数兼容性比较复杂,涉及 参数
和 返回值
。
(1) 参数:目标函数的参数数量 ≤ 源函数的参数数量(逆变)
- 目标函数(你期望的)参数越少,越容易兼容。
- 源函数(你提供的)可以忽略多余的参数。
ts
type Handler = (a: number, b: number) => void;
const h: Handler = (x, y) => { /* ... */ };
// 可以赋值一个参数更少的函数
const f1 = (x: number) => { /* ... */ };
h = f1; // ✅ 兼容!f1 可以忽略第二个参数
// 但不能赋值一个参数更多的函数
const f2 = (x: number, y: number, z: number) => { /* ... */ };
h = f2; // 不兼容!h 调用时不会传 z
2) 返回值:源函数的返回值类型必须 ≥ 目标函数的返回值类型(协变)
- 源函数可以返回"更多"信息(子类型)。
ts
interface Animal { name: string }
interface Dog extends Animal { breed: string }
type Getter = () => Animal;
const getAnimal: Getter = () => ({ name: "Pet" });
// 可以返回更具体的类型
const getDog = () => ({ name: "Rex", breed: "Lab" });
getAnimal = getDog; // 兼容!Dog 是 Animal 的子类型
3. 可选属性和额外属性
- 可选属性:目标类型中的可选属性,源类型可以没有。
- 额外属性 :源类型可以有目标类型没有的属性(但直接对象字面量有严格检查)。
ts
interface Config {
url: string;
method?: string; // 可选
}
const config: Config = { url: "http://example.com" }; // method 可选
但是,对象字面量有严格属性检查:
ts
const bad = { url: "http://", timeout: 5000 };
const config2: Config = bad; //OK,因为 bad 是变量
const config3: Config = { url: "http://", timeout: 5000 }; // 错误!字面量不能有额外属性
4. 枚举兼容性
- 枚举之间不兼容,即使值相同。
- 枚举与数字兼容(双向)。
ts
enum Color { Red, Green }
enum Size { Small, Large }
let c: Color = Color.Red;
// c = Size.Small; // 不兼容!虽然是 0
let num: number = Color.Red; // 枚举 → number
c = 1; // number → 枚举
5. 类的兼容性
- 类的实例类型兼容性基于实例成员 ,不考虑构造函数和静态成员。
- 私有成员(
private
)和受保护成员(protected
)会影响兼容性:只有来自同一个基类的类才兼容。
ts
class Animal { private name: string = "" }
class Dog { private name: string = "" }
let a: Animal;
let d: Dog;
// a = d; // 不兼容!即使结构相同,私有成员来自不同类
6. 泛型兼容性
泛型的兼容性取决于类型参数是否被使用。
ts
interface Box<T> {
value: T;
}
let box1: Box<string> = { value: "hello" };
let box2: Box<number> = { value: 123 };
// box1 = box2; // 不兼容!string 和 number 不同
但如果泛型未被使用,可能兼容:
ts
interface Empty<T> {}
let empty1: Empty<string>;
let empty2: Empty<number>;
empty1 = empty2; // 兼容!Empty<T> 是"协变"的,且结构相同
7. any 的特殊性
any
与其他所有类型都兼容(双向)。- 这是类型安全的"逃生舱",但应尽量避免。
ts
let anything: any = 123;
anything = "hello";
let str: string = anything; // any → string