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 关注的是"结构",不是"身份"。

employeenameage,满足 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.ReactNode
  • onClick 不在 ButtonProps 里,但结构化类型允许多余属性(除了直接对象字面量)

这就是为什么 React 的组合模型天然适合 TypeScript:

Component

Props(结构化类型)

Composition(结构匹配)