第 145 章:组件与 Props
学习目标
- 理解函数组件与组件复用
- 掌握 Props 的类型化定义
- 学会 children、默认值、可选 props
- 避免组件设计的常见反模式
一、组件定义
1.1 函数组件(推荐)
tsx
// 简单函数
function Welcome() {
return <h1>欢迎</h1>;
}
// 带 props
interface WelcomeProps {
name: string;
}
function Welcome({ name }: WelcomeProps) {
return <h1>欢迎,{name}</h1>;
}
// 箭头函数
const Welcome = ({ name }: WelcomeProps) => <h1>欢迎,{name}</h1>;1.2 使用组件
tsx
<Welcome name="张三" />
<Welcome name="李四" />二、Props 详解
2.1 类型化 props
tsx
interface ButtonProps {
label: string; // 必填
onClick?: () => void; // 可选
variant?: 'primary' | 'secondary' | 'danger';
disabled?: boolean;
children?: React.ReactNode; // 插槽
}
function Button({
label,
onClick,
variant = 'primary',
disabled = false,
children,
}: ButtonProps) {
const cls = `btn btn-${variant} ${disabled ? 'opacity-50' : ''}`;
return (
<button className={cls} onClick={onClick} disabled={disabled}>
{children ?? label}
</button>
);
}2.2 children 插槽
tsx
<Button>点我</Button>
// 渲染:children = "点我",label 被忽略
<Button label="提交" />
// 渲染:label = "提交",children 是 undefined2.3 任意 props
tsx
interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
title: string;
}
function Card({ title, children, ...rest }: CardProps) {
return (
<div {...rest}>
<h2>{title}</h2>
{children}
</div>
);
}2.4 必填 vs 可选
tsx
interface Props {
required: string; // 必填
optional?: number; // 可选
}三、组合模式
3.1 容器与子组件
tsx
function Card({ children }: { children: React.ReactNode }) {
return <div className="rounded border p-4">{children}</div>;
}
function CardHeader({ title }: { title: string }) {
return <h3 className="font-bold">{title}</h3>;
}
// 使用
<Card>
<CardHeader title="标题" />
<p>内容</p>
</Card>3.2 渲染函数 props(Render Props)
tsx
interface ListProps<T> {
items: T[];
render: (item: T) => React.ReactNode;
}
function List<T>({ items, render }: ListProps<T>) {
return <ul>{items.map(render)}</ul>;
}
// 使用
<List
items={users}
render={user => <li key={user.id}>{user.name}</li>}
/>四、组件组合 vs 配置
tsx
// ❌ 配置式(难以维护)
<BigForm
showAvatar
showEmail
showPhone
avatarSize={50}
emailRequired
phoneRequired
/>
// ✅ 组合式(灵活)
<BigForm>
<BigForm.Avatar size={50} />
<BigForm.Email required />
<BigForm.Phone required />
</BigForm>五、Props 传递最佳实践
5.1 明确传值
tsx
// ✅ 显式
<UserCard name="Tom" age={18} />
// ❌ 展开对象(props 不清晰)
<UserCard {...user} />5.2 避免太深嵌套
tsx
// ❌ 组件 A → B → C → D(prop drilling)
<Layout>
<Sidebar>
<Menu>
<MenuItem user={user} /> // D 用了 user
</Menu>
</Sidebar>
</Layout>
// ✅ 用 Context / Zustand5.3 反模式:不要修改 props
tsx
// ❌ 错误:props 是只读的
function Component({ items }: Props) {
items.push('new'); // 直接修改 props,React 警告
}
// ✅ 复制后再改
function Component({ items }: Props) {
const list = [...items, 'new'];
}六、组件命名与导出
tsx
// 1. 文件名与组件名一致
// UserCard.tsx
export function UserCard() {}
// 2. 一个文件一个主组件(辅助组件可放同文件)
function UserCard() {}
function UserCardAvatar() {} // 辅助
export { UserCard };
// 3. 默认导出 vs 具名导出
export default UserCard; // 默认
export { UserCard }; // 具名(推荐)七、本章小结
| 概念 | 关键 |
|---|---|
| 函数组件 | function 或箭头函数 |
| Props | 通过参数传入,只读 |
| 默认值 | 解构时 = 'value' |
| children | ReactNode 类型 |
| 反模式 | 不要修改 props,避免 prop drilling |
动手练习
- 写一个
Avatar组件,接受src、size、altprops - 写一个
Card组件,接受title、children、footer - 写一个泛型
List<T>,用 render prop 模式 - 重构一个 prop 过多的组件,用组合模式拆分
推荐阅读
下一章:第 146 章:State 与 useState →