Props
Props(属性)是组件之间传递数据的方式
什么是 Props?
Props 是你传递给 JSX 标签的属性,类似于 HTML 中的属性。 在 React 中,props 用于将数据从父组件传递到子组件。
// 父组件传递 props
function App() {
return <Greeting name="Taylor" />;
}
// 子组件接收 props
function Greeting({ name }) {
return <h1>你好,{name}!</h1>;
}
Props 是只读的。组件不能修改接收到的 props, 这使得数据流更加可预测。
传递 Props
传递字符串
// 使用双引号
<Greeting name="Taylor" />
// 使用模板字符串
<Greeting name={`Taylor`} />
传递动态值
使用花括号 {} 传递 JavaScript 表达式:
function App() {
const name = 'Taylor';
const age = 25;
return (
<UserProfile
name={name}
age={age}
isAdmin={true}
/>
);
}
传递对象
const user = {
name: 'Taylor',
age: 25,
email: 'taylor@example.com'
};
// 传递整个对象
<UserProfile user={user} />
// 使用展开运算符传递属性
<UserProfile {...user} />
使用展开运算符时,要确保对象中的属性名与组件期望的 prop 名称一致。
接收 Props
解构参数
解构是最常用和推荐的方式,代码更简洁:
// ✅ 推荐:解构参数
function UserProfile({ name, age, email }) {
return (
<div>
<h2>{name}</h2>
<p>年龄: {age}</p>
<p>邮箱: {email}</p>
</div>
);
}
使用 props 对象
// 也可以使用完整的 props 对象
function UserProfile(props) {
return (
<div>
<h2>{props.name}</h2>
<p>年龄: {props.age}</p>
<p>邮箱: {props.email}</p>
</div>
);
}
默认值
使用解构的默认值语法为 props 设置默认值:
function Button({
variant = 'primary',
size = 'medium',
children
}) {
return (
<button className={`btn btn-${variant} btn-${size}`}>
{children}
</button>
);
}
// 使用默认值
<Button>点击</Button>
// 覆盖默认值
<Button variant="secondary" size="large">
点击
</Button>
Children Prop
children 是一个特殊的 prop,用于在组件标签之间传递内容:
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
// 使用
<Card>
<h2>标题</h2>
<p>内容</p>
</Card>
嵌套组件
function Layout({ header, sidebar, children }) {
return (
<div className="layout">
<header>{header}</header>
<aside>{sidebar}</aside>
<main>{children}</main>
</div>
);
}
<Layout
header={<Header />}
sidebar={<Sidebar />}
>
<Content />
</Layout>
当你需要创建可复用的容器组件时,children prop 非常有用。
它让组件更加灵活,可以接受任意内容。
Props 穿透
Props 穿透是指通过多层组件传递 props 的过程:
// 父组件
function App() {
const [theme, setTheme] = useState('light');
return <Section theme={theme} />;
}
// 中间组件
function Section({ theme }) {
return <Article theme={theme} />;
}
// 子组件
function Article({ theme }) {
return <Content theme={theme} />;
}
当 props 需要穿透多层组件时,考虑使用 Context API 来简化数据传递。 Props 穿透会增加组件之间的耦合度。
使用 Context 替代 Props 穿透
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="light">
<Section />
</ThemeContext.Provider>
);
}
function Section() {
// 不再需要传递 theme
return <Article />;
}
function Article() {
const theme = useContext(ThemeContext);
return <Content theme={theme} />;
}
传递事件处理函数
你可以将事件处理函数作为 props 传递给子组件:
function Button({ onMouseEnter }) {
return (
<button
onMouseEnter={onMouseEnter}
>
悬停我
</button>
);
}
function App() {
const handleMouseEnter = () => {
console.log('按钮被悬停');
};
return <Button onMouseEnter={handleMouseEnter} />;
}
传递事件处理函数作为 props 时,建议使用与原生事件相同的名称(如 onMouseEnter、onClick),
这样更清晰地表明这是一个事件处理函数。
命名约定
事件处理函数的 props 通常以 on 开头,后跟事件名称:
// ✅ 好的命名
<Button onClick={handleClick} />
<Input onChange={handleChange} />
<Form onSubmit={handleSubmit} />
// ❌ 不好的命名
<Button click={handleClick} />
<Input change={handleChange} />
Props 是只读的
组件绝不应该修改接收到的 props:
// ❌ 错误:直接修改 props
function User({ name }) {
name = name.toUpperCase(); // 不要这样做!
return <h1>{name}</h1>;
}
// ✅ 正确:使用本地状态
function User({ name }) {
const [displayName, setDisplayName] = useState(name);
return <h1>{displayName}</h1>;
}
React 组件应该是纯函数:对于相同的输入 (props),总是返回相同的输出 (JSX)。 不修改 props 可以保持组件的可预测性。
TypeScript 类型检查
使用 TypeScript 为 props 定义类型,可以获得更好的开发体验:
interface ButtonProps {
variant: 'primary' | 'secondary' | 'danger';
size?: 'small' | 'medium' | 'large';
onClick: () => void;
children: React.ReactNode;
}
function Button({
variant,
size = 'medium',
onClick,
children
}: ButtonProps) {
return (
<button
className={`btn btn-${variant} btn-${size}`}
onClick={onClick}
>
{children}
</button>
);
}
常用类型
interface Props {
// 基本类型
title: string;
count: number;
isActive: boolean;
// 可选类型
subtitle?: string;
// 联合类型
status: 'success' | 'error' | 'loading';
// 数组类型
items: string[];
// 对象类型
user: {
name: string;
age: number;
};
// 函数类型
onClick: (event: MouseEvent) => void;
onChange: (value: string) => void;
// React 节点
children: React.ReactNode;
// React 元素
icon: React.ReactElement;
// 任意类型(不推荐)
data: any;
}
常见模式
条件渲染
function Alert({ type, message, show }) {
if (!show) return null;
return (
<div className={`alert alert-${type}`}>
{message}
</div>
);
}
<Alert type="error" message="出错了" show={hasError} />
渲染列表
function List({ items }) {
return (
<ul>
{items.map((item) => (
<li key={item.id}>
{item.name}
</li>
))}
</ul>
);
}
组合组件
function Tabs({ children }) {
return <div className="tabs">{children}</div>;
}
function Tab({ label, children }) {
return <div className="tab">{children}</div>;
}
// 使用
<Tabs>
<Tab label="Tab 1">内容 1</Tab>
<Tab label="Tab 2">内容 2</Tab>
</Tabs>
组合组件模式让组件更加灵活,可以实现类似 HTML 的声明式 API。
常见问题
Props 没有更新?
确保你创建了新的对象/数组,而不是修改现有的:
// ❌ 错误:修改现有数组
items.push(newItem);
setItems(items);
// ✅ 正确:创建新数组
setItems([...items, newItem]);
子组件没有重新渲染?
检查是否正确地传递了 props,以及 props 的引用是否发生了变化。
如何传递所有 props?
使用展开运算符传递所有 props:
function StyledButton({ className, ...props }) {
return (
<button
className={`btn ${className}`}
{...props}
/>
);
}
相关概念
- State - 组件内部状态
- Context - 跨组件共享数据
- 事件处理 - 处理用户交互
- useContext - Context Hook