人人都会AI编程

18.1 组件 Props 类型定义、默认值、必填校验

更新时间:2026-07-11

Props 是 React 组件对外的公开接口。在 JavaScript 中,Props 缺乏约束,调用方可能遗漏必填属性或传入错误类型,导致运行时 bug 难以排查。TypeScript 为 Props 提供了静态类型检查能力,让接口契约在编译阶段就能得到保证。

定义 Props 类型

使用 interfacetype 定义组件的 Props 类型,命名通常遵循 组件名 + Props 的约定:

interface UserCardProps {
  name: string;
  email: string;
  age?: number;           // 可选属性
  onEdit?: (id: string) => void;
  className?: string;
}

然后作为函数组件的参数类型:

function UserCard({ name, email, age, onEdit, className }: UserCardProps) {
  return <div className={className}>...</div>;
}

必填与可选校验

TypeScript 通过在属性名后添加 ? 来标记可选,不加则默认为必填。当调用组件时,如果缺少必填属性,编辑器会直接报错:

// ❌ 缺少必填的 email
<UserCard name="张三" />  // 类型“{ name: string; }”缺少属性“email”

这比运行时的 PropTypes 更早发现问题,并且提供了智能提示和自动补全。

设置默认值

现代 React 函数组件中,推荐直接在解构参数时指定默认值,而不使用 defaultProps(React 18+ 后官方已不推荐,React 19 可能废弃)。这样 TypeScript 也能正确推断默认值后的类型。

interface PaginationProps {
  current: number;
  pageSize?: number;
  onChange: (page: number) => void;
}

function Pagination({ current, pageSize = 10, onChange }: PaginationProps) {
  // pageSize 有默认值 10,调用方可不传
  return <div>...</div>;
}

// 使用
<Pagination current={1} onChange={(p) => console.log(p)} /> // pageSize 自动取 10

需要注意:当设置了默认值后,TypeScript 会自动将该属性视为可选。因此在定义 PaginationProps 时,pageSize 可以标记为可选(pageSize?: number),但如果标记为必填,那么即使有默认值,调用方也会被要求显式传入值(类型不匹配)。实践中两种方式皆可,推荐将具有默认值的属性标记为可选,让接口设计更清晰。

常见 Props 类型示例

import { ReactNode, CSSProperties, MouseEvent } from 'react';

interface ButtonProps {
  children: ReactNode;                    // 子节点,可以是文本、元素等
  variant?: 'primary' | 'default';       // 字面量联合类型
  disabled?: boolean;
  onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
  style?: CSSProperties;                 // 内联样式对象
  className?: string;
}

function Button({
  children,
  variant = 'default',
  disabled = false,
  onClick,
  style,
  className,
}: ButtonProps) {
  return (
    <button
      className={`btn btn-${variant} ${className ?? ''}`}
      disabled={disabled}
      onClick={onClick}
      style={style}
    >
      {children}
    </button>
  );
}

处理特殊场景:泛型组件

如果 Props 中有类型依赖外部的数据类型,可以用泛型组件:

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

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

// 使用时自动推断 T
<List items={[{ id: 1, name: 'Apple' }]} renderItem={(item) => <li>{item.name}</li>} />

运行时校验补充(可选)

TypeScript 的类型检查只在编译期生效,运行时无法保证(比如 API 返回的数据可能偏离类型定义)。对于关键组件,可以结合 prop-types 做运行时兜底,但现代 React 项目更推荐使用数据校验库(如 Zod)在数据入口处进行验证,而不是在组件 Props 层。一般情况下,TypeScript 的静态检查足以覆盖绝大多数场景。

最佳实践总结

  • 为每个组件明确定义 Props 类型,作为组件文档的一部分。
  • 必填属性不加 ?,让调用方无法遗漏。
  • 具有合理默认值的属性使用可选 + 参数默认值。
  • 避免过度使用 any,如果类型复杂,优先使用泛型或具体类型。
  • 函数类型、事件类型使用 React 提供的内置类型(如 MouseEventHandlerChangeEventHandler)。

通过 TypeScript 的 Props 约束,你能在编码阶段就避免大量常见的传参错误,显著提升代码的健壮性和可维护性。