Appearance
Props类型定义
概述
Props是React组件间通信的主要方式。在TypeScript中,合理定义Props类型能够提供类型安全、自动补全和文档化的好处。本文将全面介绍React Props的各种类型定义方式和最佳实践。
基础Props类型
简单Props
typescript
// 基本类型Props
interface GreetingProps {
name: string;
age: number;
isActive: boolean;
}
const Greeting = ({ name, age, isActive }: GreetingProps) => {
return (
<div>
<h1>Hello, {name}!</h1>
<p>Age: {age}</p>
<p>Status: {isActive ? 'Active' : 'Inactive'}</p>
</div>
);
};
// 使用
<Greeting name="Alice" age={30} isActive={true} />可选Props
typescript
interface UserCardProps {
name: string;
email: string;
phone?: string; // 可选
address?: string; // 可选
avatar?: string; // 可选
}
const UserCard = ({
name,
email,
phone,
address,
avatar = '/default-avatar.png', // 默认值
}: UserCardProps) => {
return (
<div className="user-card">
<img src={avatar} alt={name} />
<h2>{name}</h2>
<p>{email}</p>
{phone && <p>Phone: {phone}</p>}
{address && <p>Address: {address}</p>}
</div>
);
};只读Props
typescript
interface ConfigProps {
readonly apiUrl: string;
readonly timeout: number;
readonly headers: Readonly<Record<string, string>>;
}
const ApiClient = ({ apiUrl, timeout, headers }: ConfigProps) => {
// headers.Authorization = 'new-token'; // ❌ 错误: 不能修改
return <div>API URL: {apiUrl}</div>;
};Children Props
ReactNode类型
typescript
// 最常用的Children类型
interface ContainerProps {
children: React.ReactNode;
}
const Container = ({ children }: ContainerProps) => {
return <div className="container">{children}</div>;
};
// 使用 - 可以传递任何可渲染的内容
<Container>
<h1>Title</h1>
<p>Content</p>
{/* 字符串、数字、null、undefined、数组等 */}
</Container>单个元素Children
typescript
// ReactElement - 只接受单个React元素
interface WrapperProps {
children: React.ReactElement;
}
const Wrapper = ({ children }: WrapperProps) => {
return <div className="wrapper">{children}</div>;
};
// 使用
<Wrapper>
<div>Single element</div>
</Wrapper>
// ❌ 错误 - 不能传递多个元素
<Wrapper>
<div>Element 1</div>
<div>Element 2</div>
</Wrapper>特定类型Children
typescript
// 只接受特定类型的元素
interface ListProps {
children: React.ReactElement<ItemProps> | React.ReactElement<ItemProps>[];
}
interface ItemProps {
value: string;
}
const Item = ({ value }: ItemProps) => <li>{value}</li>;
const List = ({ children }: ListProps) => {
return <ul>{children}</ul>;
};
// 使用
<List>
<Item value="Item 1" />
<Item value="Item 2" />
</List>Render Props
typescript
// 函数Children
interface DataProviderProps<T> {
data: T;
children: (data: T) => React.ReactNode;
}
function DataProvider<T>({ data, children }: DataProviderProps<T>) {
return <>{children(data)}</>;
}
// 使用
interface User {
name: string;
email: string;
}
<DataProvider data={{ name: 'Alice', email: 'alice@example.com' }}>
{(user) => (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
)}
</DataProvider>函数Props
事件处理器
typescript
interface ButtonProps {
// 基本事件处理器
onClick: () => void;
onHover?: () => void;
}
interface ButtonWithParamsProps {
// 带参数的事件处理器
onClick: (id: string) => void;
onDoubleClick: (event: React.MouseEvent, id: string) => void;
}
interface AsyncButtonProps {
// 异步事件处理器
onClick: () => Promise<void>;
onSubmit: (data: FormData) => Promise<boolean>;
}
const Button = ({ onClick, onHover }: ButtonProps) => {
return (
<button onClick={onClick} onMouseEnter={onHover}>
Click me
</button>
);
};回调Props
typescript
interface FormProps {
// 成功回调
onSuccess: (data: FormData) => void;
// 错误回调
onError: (error: Error) => void;
// 进度回调
onProgress?: (progress: number) => void;
// 取消回调
onCancel?: () => void;
}
interface FormData {
name: string;
email: string;
}
const Form = ({ onSuccess, onError, onProgress, onCancel }: FormProps) => {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
// 模拟上传进度
onProgress?.(50);
const data: FormData = {
name: 'Alice',
email: 'alice@example.com',
};
onProgress?.(100);
onSuccess(data);
} catch (error) {
onError(error as Error);
}
};
return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
<button type="button" onClick={onCancel}>Cancel</button>
</form>
);
};对象和数组Props
对象Props
typescript
// 简单对象
interface UserProps {
user: {
id: string;
name: string;
email: string;
};
}
// 使用接口定义对象
interface User {
id: string;
name: string;
email: string;
profile?: {
avatar: string;
bio: string;
};
}
interface UserProfileProps {
user: User;
}
const UserProfile = ({ user }: UserProfileProps) => {
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
{user.profile && (
<div>
<img src={user.profile.avatar} alt="Avatar" />
<p>{user.profile.bio}</p>
</div>
)}
</div>
);
};数组Props
typescript
// 基本数组
interface ListProps {
items: string[];
}
// 对象数组
interface User {
id: string;
name: string;
}
interface UserListProps {
users: User[];
}
const UserList = ({ users }: UserListProps) => {
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};
// 只读数组
interface ReadonlyListProps {
items: readonly string[];
users: ReadonlyArray<User>;
}复杂嵌套类型
typescript
interface Post {
id: string;
title: string;
content: string;
author: {
id: string;
name: string;
avatar: string;
};
comments: Array<{
id: string;
content: string;
author: {
id: string;
name: string;
};
replies: Array<{
id: string;
content: string;
authorId: string;
}>;
}>;
tags: string[];
metadata: {
views: number;
likes: number;
createdAt: Date;
updatedAt: Date;
};
}
interface PostDetailProps {
post: Post;
onLike: (postId: string) => void;
onComment: (postId: string, content: string) => void;
}
const PostDetail = ({ post, onLike, onComment }: PostDetailProps) => {
return (
<article>
<h1>{post.title}</h1>
<div className="author">
<img src={post.author.avatar} alt={post.author.name} />
<span>{post.author.name}</span>
</div>
<p>{post.content}</p>
<div className="tags">
{post.tags.map((tag) => (
<span key={tag}>{tag}</span>
))}
</div>
<div className="metadata">
<span>Views: {post.metadata.views}</span>
<span>Likes: {post.metadata.likes}</span>
</div>
<button onClick={() => onLike(post.id)}>Like</button>
</article>
);
};联合类型和字面量Props
字面量联合类型
typescript
interface ButtonProps {
variant: 'primary' | 'secondary' | 'danger' | 'success';
size: 'small' | 'medium' | 'large';
shape?: 'square' | 'rounded' | 'circle';
}
const Button = ({ variant, size, shape = 'rounded' }: ButtonProps) => {
return (
<button className={`btn-${variant} btn-${size} btn-${shape}`}>
Button
</button>
);
};
// 使用
<Button variant="primary" size="medium" />条件Props
typescript
// 根据一个prop的值,其他props可能是必需的
type IconButtonProps =
| {
icon: React.ReactNode;
label?: string;
'aria-label': string; // 必须提供
}
| {
icon?: never;
label: string; // 必须提供
'aria-label'?: string;
};
const IconButton = (props: IconButtonProps) => {
return (
<button aria-label={props['aria-label']}>
{props.icon}
{props.label}
</button>
);
};
// 使用
<IconButton icon={<Icon />} aria-label="Close" /> // ✅
<IconButton label="Submit" /> // ✅
// <IconButton icon={<Icon />} /> // ❌ 错误: 缺少aria-label互斥Props
typescript
// 使用XOR模式
type XOR<T, U> = T | U extends object
? (Without<T, U> & U) | (Without<U, T> & T)
: T | U;
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
// 应用
type LoadingProps = XOR<
{ isLoading: true; data?: never },
{ isLoading?: false; data: any }
>;
interface DataDisplayProps extends LoadingProps {
title: string;
}
const DataDisplay = (props: DataDisplayProps) => {
if (props.isLoading) {
return <div>Loading...</div>;
}
return <div>{props.data}</div>;
};
// 使用
<DataDisplay title="Data" isLoading={true} /> // ✅
<DataDisplay title="Data" data={[1, 2, 3]} /> // ✅
// <DataDisplay title="Data" isLoading={true} data={[1, 2, 3]} /> // ❌ 错误泛型Props
基本泛型Props
typescript
// 泛型List组件
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
keyExtractor: (item: T) => string | number;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map((item) => (
<li key={keyExtractor(item)}>{renderItem(item)}</li>
))}
</ul>
);
}
// 使用
interface User {
id: number;
name: string;
}
const users: User[] = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
];
<List
items={users}
renderItem={(user) => <span>{user.name}</span>}
keyExtractor={(user) => user.id}
/>带约束的泛型Props
typescript
// 泛型约束
interface WithId {
id: string | number;
}
interface TableProps<T extends WithId> {
data: T[];
columns: Array<{
key: keyof T;
title: string;
render?: (value: T[keyof T], row: T) => React.ReactNode;
}>;
onRowClick?: (row: T) => void;
}
function Table<T extends WithId>({
data,
columns,
onRowClick,
}: TableProps<T>) {
return (
<table>
<thead>
<tr>
{columns.map((col) => (
<th key={String(col.key)}>{col.title}</th>
))}
</tr>
</thead>
<tbody>
{data.map((row) => (
<tr key={row.id} onClick={() => onRowClick?.(row)}>
{columns.map((col) => (
<td key={String(col.key)}>
{col.render ? col.render(row[col.key], row) : String(row[col.key])}
</td>
))}
</tr>
))}
</tbody>
</table>
);
}
// 使用
interface Product {
id: number;
name: string;
price: number;
}
<Table
data={products}
columns={[
{ key: 'name', title: 'Name' },
{ key: 'price', title: 'Price', render: (value) => `$${value}` },
]}
onRowClick={(product) => console.log(product)}
/>组件Props继承
继承HTML元素Props
typescript
// 方式1: 使用React.ComponentPropsWithoutRef
interface ButtonProps extends React.ComponentPropsWithoutRef<'button'> {
variant?: 'primary' | 'secondary';
}
const Button = ({ variant = 'primary', children, ...props }: ButtonProps) => {
return (
<button {...props} className={`btn-${variant}`}>
{children}
</button>
);
};
// 使用 - 可以传递所有button的原生属性
<Button onClick={() => {}} disabled type="submit" aria-label="Submit">
Click me
</Button>
// 方式2: 使用React.ComponentPropsWithRef(包含ref)
interface InputProps extends React.ComponentPropsWithRef<'input'> {
label?: string;
}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ label, ...props }, ref) => {
return (
<div>
{label && <label>{label}</label>}
<input ref={ref} {...props} />
</div>
);
}
);继承其他组件Props
typescript
// 定义基础组件
interface BaseButtonProps {
variant: 'primary' | 'secondary';
size: 'small' | 'large';
}
const BaseButton = ({ variant, size, children }: BaseButtonProps & { children: React.ReactNode }) => {
return <button className={`btn-${variant} btn-${size}`}>{children}</button>;
};
// 扩展基础组件Props
interface IconButtonProps extends BaseButtonProps {
icon: React.ReactNode;
iconPosition?: 'left' | 'right';
}
const IconButton = ({ icon, iconPosition = 'left', ...baseProps }: IconButtonProps) => {
return (
<BaseButton {...baseProps}>
{iconPosition === 'left' && icon}
{baseProps.children}
{iconPosition === 'right' && icon}
</BaseButton>
);
};Omit和Pick
typescript
// 使用Omit排除某些Props
interface User {
id: string;
name: string;
email: string;
password: string;
}
interface PublicUserProps {
user: Omit<User, 'password'>;
}
const PublicProfile = ({ user }: PublicUserProps) => {
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
{/* user.password 不可访问 */}
</div>
);
};
// 使用Pick选择某些Props
interface UserFormProps {
user: Pick<User, 'name' | 'email'>;
}
const UserForm = ({ user }: UserFormProps) => {
return (
<form>
<input defaultValue={user.name} />
<input defaultValue={user.email} />
</form>
);
};Props验证
运行时验证
typescript
// 使用自定义验证函数
interface ValidationResult {
isValid: boolean;
error?: string;
}
interface ValidatedInputProps {
value: string;
validate: (value: string) => ValidationResult;
onValidationChange: (result: ValidationResult) => void;
}
const ValidatedInput = ({ value, validate, onValidationChange }: ValidatedInputProps) => {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const result = validate(e.target.value);
onValidationChange(result);
};
return <input value={value} onChange={handleChange} />;
};
// 使用
<ValidatedInput
value=""
validate={(value) => ({
isValid: value.length > 0,
error: value.length === 0 ? 'Required' : undefined,
})}
onValidationChange={(result) => console.log(result)}
/>Zod集成
typescript
import { z } from 'zod';
// 定义schema
const UserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().min(0).max(150),
});
// 从schema推断类型
type User = z.infer<typeof UserSchema>;
interface UserFormProps {
initialData?: Partial<User>;
onSubmit: (data: User) => void;
}
const UserForm = ({ initialData, onSubmit }: UserFormProps) => {
const handleSubmit = (data: unknown) => {
try {
const validated = UserSchema.parse(data);
onSubmit(validated);
} catch (error) {
console.error('Validation error:', error);
}
};
return <form onSubmit={(e) => {/* ... */}}>Form</form>;
};最佳实践
Props命名规范
typescript
// 1. 布尔值使用is/has/should前缀
interface ComponentProps {
isLoading: boolean;
hasError: boolean;
shouldRender: boolean;
disabled: boolean; // 或使用isDisabled
}
// 2. 事件处理器使用on前缀
interface EventProps {
onClick: () => void;
onSubmit: (data: any) => void;
onError: (error: Error) => void;
}
// 3. Render props使用render前缀
interface RenderProps {
renderHeader: () => React.ReactNode;
renderFooter: (data: any) => React.ReactNode;
}Props接口组织
typescript
// 按功能分组
interface ButtonProps {
// 外观
variant: 'primary' | 'secondary';
size: 'small' | 'medium' | 'large';
fullWidth?: boolean;
// 内容
children: React.ReactNode;
icon?: React.ReactNode;
// 交互
onClick?: () => void;
onHover?: () => void;
disabled?: boolean;
// 可访问性
'aria-label'?: string;
'aria-describedby'?: string;
}文档化Props
typescript
/**
* Button组件
*
* @example
* ```tsx
* <Button variant="primary" size="medium" onClick={() => alert('Clicked')}>
* Click me
* </Button>
* ```
*/
interface ButtonProps {
/**
* 按钮样式变体
* @default 'primary'
*/
variant?: 'primary' | 'secondary' | 'danger';
/**
* 按钮大小
* @default 'medium'
*/
size?: 'small' | 'medium' | 'large';
/**
* 点击事件处理器
*/
onClick?: () => void;
/**
* 按钮内容
*/
children: React.ReactNode;
}Props类型定义是React TypeScript开发的基础,合理的Props类型设计能够提升代码质量和开发体验。