Props 是 React 组件对外的公开接口。在 JavaScript 中,Props 缺乏约束,调用方可能遗漏必填属性或传入错误类型,导致运行时 bug 难以排查。TypeScript 为 Props 提供了静态类型检查能力,让接口契约在编译阶段就能得到保证。
定义 Props 类型
使用 interface 或 type 定义组件的 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 提供的内置类型(如
MouseEventHandler、ChangeEventHandler)。
通过 TypeScript 的 Props 约束,你能在编码阶段就避免大量常见的传参错误,显著提升代码的健壮性和可维护性。