TypeScript 类型兼容性规则速查
· 4 min read
TS 类型兼容性核心是 结构化类型——多的可以赋值给少的,duck typing 是关键。
- 核心规则:成员多的类型可以赋值给成员少的,反之不行
- 特殊类型:
any万能,never万不能 - 函数参数:默认双变,开 strictFunctionTypes 后变逆变
- Class:私有成员相互不兼容(除继承外 )
- Generic:未实例化的泛型可以互相兼容
核心规则:多的赋值给少的
TS 是结构化类型(structural typing),不是 Java/C# 那样的名义类型(nominal typing)。两个类型"形状兼容"就能互相赋值,跟声明无关。
interface X { a: number }
interface Y { a: number; b: number }
let x: X = { a: 1 }
let y: Y = { a: 1, b: 2 }
x = y // OK:Y 有 X 的所有字段
y = x // Error:X 缺 b
只要 Y 的成员包含 X 的成员,就能赋值;反过来不行。这条规则贯穿整个 TS 类型系统。
基本类型兼容性
let str: string = 'hello'
let num: number
num = str // Error:string 不是 number
str = num // Error:number 不是 string
两个基本类型除非完全一致,否则不兼容。
特殊类型
| 类型 | 能赋给别人 | 别人能赋给它 |
|---|---|---|
any | 所有(除 never) | 所有 |
never | 没有任何类型 | 所有 |
void | 通常不赋值 | 只有 undefined/null |
unknown | 没有任何类型 | 所有(需 narrow) |
tip
unknown 是 any 的安全替代——能接受任何值,但用之前必须 narrow。
Interface 兼容性
"多的可以赋值给少的"在 Interface 上同样适用:
interface Point2D { x: number; y: number }
interface Point3D { x: number; y: number; z: number }
let p2d: Point2D = { x: 1, y: 2 }
let p3d: Point3D = { x: 1, y: 2, z: 3 }
p2d = p3d // OK
p3d = p2d // Error
Class 兼容性
类在结构上跟 interface 类似,但也有限制:
class A { id = 1; name = 'a' }
class B { id = 2; name = 'b'; extra = true }
let a = new A()
let b = new B()
a = b // OK:B 字段多
b = a // Error:A 缺 extra
私有成员打破兼容性:
class A {
private x = 1
}
class B {
private x = 1
}
let a = new A()
let b = new B()
a = b // Error:私有成员来自不同声明
但父子继承关系不受影响:
class Parent {
private x = 1
}
class Child extends Parent {
y = 2
}
let p: Parent = new Child() // OK:子类满足父类形状
函数兼容性
参数个数
let handler1 = (a: number) => {}
let handler2 = (a: number, b: number) => {}
handler2 = handler1 // OK
handler1 = handler2 // Error:handler2 参数多了
warning
开 strictFunctionTypes 后,函数参数是逆变(少的可以赋值给多的)——这条规则反过来。默认是双变(不检查)。
tsconfig.json
{
"strictFunctionTypes": true
}
参数类型
参数类型必须相互兼容,按结构化类型规则:
type Handler = (a: number, b: number) => void
let handler3 = (a: string, b: string) => {}
handler3 = (Handler) // Error:string 不是 number
返回值
返回值走协变:少的可以赋值给多的。
let f = () => ({ name: 'Kimi' })
let g = () => ({ name: 'Kimi', age: 20 })
f = g // OK
g = f // Error:f 缺 age
泛型兼容性
未实例化的泛型互相兼容:
interface Empty<T> {}
let e1: Empty<number> = {}
let e2: Empty<string> = {}
e1 = e2 // OK:结构相同
但用了类型变量就不一样了:
interface Box<T> {
value: T
}
let b1: Box<number> = { value: 1 }
let b2: Box<string> = { value: 'a' }
b1 = b2 // Error:Box<number> 和 Box<string> 不兼容
泛型只有在实例化之后才参与类型检查。
References
- TypeScript Handbook: Type Compatibility —— 官方手册
- TypeScript Deep Dive: Variance —— 协变/逆变深度讲解