TypeScript 在 React 中的最高价值不是"给 props 加类型"。

而是:

类型先行:先定义组件允许表达什么,再写实现。


类型先行

// ❌ 先写实现,再补类型
function Modal(props) {
  return (
    <div className="modal">
      <div className="modal-header">{props.title}</div>
      <div className="modal-body">{props.children}</div>
      <div className="modal-footer">
        <button onClick={props.onClose}>关闭</button>
      </div>
    </div>
  )
}
// 然后再补类型,但已经晚了——API 设计已经定型

// ✅ 先定义类型,再写实现
interface ModalProps {
  title: string
  children: React.ReactNode
  onClose: () => void
  size?: 'sm' | 'md' | 'lg'
  closeOnOverlay?: boolean
}

function Modal({ title, children, onClose, size = 'md', closeOnOverlay = true }: ModalProps) {
  // ...
}

类型先行的好处:

  • 在写实现之前,就知道组件的 API 长什么样
  • 类型约束了组件可以做什么、不可以做什么
  • 使用者在调用时就能看到所有选项

用类型设计不容易被错误使用的 API

原则 1:用 union 类型限制选项

// ❌ 字符串,容易拼错
interface ButtonProps {
  variant: string
}

// ✅ union 类型,拼错就报错
interface ButtonProps {
  variant: 'primary' | 'secondary' | 'danger'
}

原则 2:用 discriminated union 处理互斥状态

// ❌ 容易同时传 icon 和 loading,语义冲突
interface ButtonProps {
  icon?: React.ReactNode
  loading?: boolean
}

// ✅ discriminated union,互斥
type ButtonProps = { loading: true; icon?: never } | { loading?: false; icon?: React.ReactNode }

原则 3:用泛型让类型贯穿输入输出

interface ListProps<T> {
  items: T[]
  renderItem: (item: T, index: number) => React.ReactNode
  keyExtractor: (item: T) => string
}

function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={keyExtractor(item)}>{renderItem(item, index)}</li>
      ))}
    </ul>
  )
}

使用时:

interface User {
  id: string
  name: string
}

;<List
  items={users}
  renderItem={(user) => <span>{user.name}</span>} // user 的类型是 User
  keyExtractor={(user) => user.id}
/>

renderItemkeyExtractor 的参数类型自动推导为 User


设计流程

类型驱动的组件设计流程:

需求

状态空间(这个组件可能处于哪些状态?)

类型(用 discriminated union 描述状态)

Props API(组件接受什么参数?)

组件实现

核心洞察

不是给已经写好的 JavaScript 补类型,而是让类型参与设计。

好的 API 通过类型把错误使用变得困难。

类型约束了组件可以做什么、不可以做什么。使用者在调用时,TypeScript 会告诉他们哪些用法是合法的,哪些是非法的。