Skip to main content

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

unknownany 的安全替代——能接受任何值,但用之前必须 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

  1. TypeScript Handbook: Type Compatibility —— 官方手册
  2. TypeScript Deep Dive: Variance —— 协变/逆变深度讲解