TypeScript 的类型系统有一个核心特征:结构化类型(Structural Typing)。
鸭子类型
interface User {
name: string
age: number
}
interface Employee {
name: string
age: number
department: string
}
const employee: Employee = { name: 'Alice', age: 25, department: 'Engineering' }
const user: User = employee // ✅ 可以赋值
为什么 employee 可以赋值给 user?
因为 TypeScript 关注的是"结构",不是"身份"。
employee 有 name 和 age,满足 User 的结构要求,所以可以赋值。TypeScript 不关心 employee 是不是"真的" User。
这就是结构化类型:
TypeScript 不是问"你是不是这个类型?",而是在问"你的结构是否满足这个类型?"
与名义类型的对比
一些语言(如 Java、C#)使用名义类型:
// Java
interface User { ... }
interface Employee { ... }
// 即使 Employee 有 User 的所有方法,也不能直接赋值
// 因为它们是不同的"名义"
TypeScript 选择结构化类型,是因为它要描述的是 JavaScript 的运行时行为。JavaScript 不关心对象的"身份",只关心对象"有没有某个属性"。
Excess Property Checking
TypeScript 有一个特殊的检查:当直接传递对象字面量时,会检查多余的属性。
interface User {
name: string
age: number
}
// ❌ 直接传递对象字面量,多余属性会报错
const user: User = { name: 'Alice', age: 25, email: 'alice@test.com' }
// 编译错误:对象字面量只能指定已知属性
// ✅ 先赋值给变量,再传递,不会报错
const data = { name: 'Alice', age: 25, email: 'alice@test.com' }
const user: User = data // ✅
这是 TypeScript 的一个特殊规则,防止拼写错误。但在结构化类型系统中,变量之间的赋值只检查结构是否匹配。
React Props 为什么天然适合结构化类型
React 的组件模型:
Component
↓
接受 Props(结构)
↓
返回 JSX
Props 本质上是一个对象,组件只关心"这个对象有没有我需要的属性"。
interface ButtonProps {
variant: 'primary' | 'secondary'
children: React.ReactNode
}
function Button({ variant, children }: ButtonProps) {
return <button className={`btn btn-${variant}`}>{children}</button>
}
使用时:
<Button variant="primary" onClick={() => console.log('clicked')}>
Click
</Button>
TypeScript 检查:
variant必须是'primary'或'secondary'children必须是React.ReactNodeonClick不在ButtonProps里,但结构化类型允许多余属性(除了直接对象字面量)
这就是为什么 React 的组合模型天然适合 TypeScript:
Component
↓
Props(结构化类型)
↓
Composition(结构匹配)