React 属性 Props
概述
Props(properties 的缩写)是 React 组件间传递数据的机制。Props 使得组件可复用且灵活,是构建动态 React 应用的基础。本章将深入学习 Props 的使用方法、验证和最佳实践。
🔄 Props 基础
什么是 Props
jsx
// Props 是从父组件传递给子组件的数据
function Greeting({ name, age }) {
return (
<div>
<h2>你好,{name}!</h2>
<p>你今年 {age} 岁了</p>
</div>
);
}
function App() {
return (
<div>
<Greeting name="张三" age={25} />
<Greeting name="李四" age={30} />
</div>
);
}Props 的特点
- 只读性:组件不能修改自己的 props
- 单向数据流:数据从父组件流向子组件
- 动态性:props 可以是变量、表达式或函数
📦 Props 类型
基本数据类型
jsx
function UserCard({
name, // string
age, // number
isActive, // boolean
hobbies, // array
profile, // object
onEdit // function
}) {
return (
<div className={`user-card ${isActive ? 'active' : 'inactive'}`}>
<h3>{name}</h3>
<p>年龄: {age}</p>
<p>状态: {isActive ? '在线' : '离线'}</p>
<div className="hobbies">
<h4>爱好:</h4>
<ul>
{hobbies.map((hobby, index) => (
<li key={index}>{hobby}</li>
))}
</ul>
</div>
<p>邮箱: {profile.email}</p>
<button onClick={() => onEdit(profile.id)}>
编辑用户
</button>
</div>
);
}
// 使用组件
function App() {
const user = {
id: 1,
email: 'zhangsan@example.com',
address: '北京市朝阳区'
};
const handleEdit = (userId) => {
console.log(`编辑用户 ID: ${userId}`);
};
return (
<UserCard
name="张三"
age={28}
isActive={true}
hobbies={['读书', '游泳', '编程']}
profile={user}
onEdit={handleEdit}
/>
);
}特殊的 Props
children 属性
jsx
// children 是特殊的 prop,表示组件的子元素
function Card({ title, children }) {
return (
<div className="card">
<h3 className="card-title">{title}</h3>
<div className="card-content">
{children}
</div>
</div>
);
}
function App() {
return (
<div>
<Card title="用户信息">
<p>姓名: 张三</p>
<p>邮箱: zhangsan@example.com</p>
<button>编辑</button>
</Card>
<Card title="产品列表">
<ul>
<li>产品 A</li>
<li>产品 B</li>
<li>产品 C</li>
</ul>
</Card>
</div>
);
}render props 模式
jsx
function DataProvider({ render, url }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(data => {
setData(data);
setLoading(false);
});
}, [url]);
return render({ data, loading });
}
// 使用 render props
function App() {
return (
<DataProvider
url="/api/users"
render={({ data, loading }) => (
<div>
{loading ? (
<p>加载中...</p>
) : (
<ul>
{data?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
)}
/>
);
}⚙️ Props 默认值
使用默认参数
jsx
function Button({
children,
type = 'button',
variant = 'primary',
size = 'medium',
disabled = false,
onClick = () => {}
}) {
const className = `btn btn-${variant} btn-${size}`;
return (
<button
type={type}
className={className}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
}
// 使用默认值
function App() {
return (
<div>
<Button>默认按钮</Button>
<Button variant="secondary" size="large">
次要按钮
</Button>
<Button variant="danger" disabled>
禁用按钮
</Button>
</div>
);
}使用 defaultProps(类组件)
jsx
class Welcome extends Component {
static defaultProps = {
name: '访客',
showTime: false
};
render() {
const { name, showTime } = this.props;
return (
<div>
<h1>欢迎,{name}!</h1>
{showTime && (
<p>当前时间: {new Date().toLocaleString()}</p>
)}
</div>
);
}
}🔍 Props 解构和传递
Props 解构
jsx
// 基础解构
function UserInfo({ user: { name, email, age } }) {
return (
<div>
<h2>{name}</h2>
<p>邮箱: {email}</p>
<p>年龄: {age}</p>
</div>
);
}
// 重命名解构
function ProductCard({
product: {
name: productName,
price: productPrice
},
currency = '¥'
}) {
return (
<div>
<h3>{productName}</h3>
<p>价格: {currency}{productPrice}</p>
</div>
);
}
// 剩余参数
function Input({ label, error, ...inputProps }) {
return (
<div className="form-group">
<label>{label}</label>
<input {...inputProps} />
{error && <span className="error">{error}</span>}
</div>
);
}
// 使用示例
function LoginForm() {
return (
<form>
<Input
label="用户名"
type="text"
placeholder="请输入用户名"
required
/>
<Input
label="密码"
type="password"
placeholder="请输入密码"
error="密码长度至少 6 位"
required
/>
</form>
);
}Props 传递模式
jsx
// 透传 props
function FormInput(props) {
return <input className="form-input" {...props} />;
}
// 选择性传递
function CustomButton({ variant, size, children, ...buttonProps }) {
const className = `btn btn-${variant} btn-${size}`;
return (
<button className={className} {...buttonProps}>
{children}
</button>
);
}
// 组合传递
function IconButton({ icon, children, ...props }) {
return (
<CustomButton {...props}>
<span className="icon">{icon}</span>
{children}
</CustomButton>
);
}🛡️ Props 验证
PropTypes(开发环境)
jsx
import PropTypes from 'prop-types';
function UserProfile({
user,
showEmail,
onEdit,
theme
}) {
return (
<div className={`user-profile theme-${theme}`}>
<h2>{user.name}</h2>
<p>年龄: {user.age}</p>
{showEmail && <p>邮箱: {user.email}</p>}
<button onClick={() => onEdit(user.id)}>编辑</button>
</div>
);
}
UserProfile.propTypes = {
user: PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
email: PropTypes.string
}).isRequired,
showEmail: PropTypes.bool,
onEdit: PropTypes.func.isRequired,
theme: PropTypes.oneOf(['light', 'dark'])
};
UserProfile.defaultProps = {
showEmail: false,
theme: 'light'
};TypeScript 类型定义
tsx
interface User {
id: number;
name: string;
age: number;
email?: string;
}
interface UserProfileProps {
user: User;
showEmail?: boolean;
onEdit: (userId: number) => void;
theme?: 'light' | 'dark';
}
function UserProfile({
user,
showEmail = false,
onEdit,
theme = 'light'
}: UserProfileProps) {
return (
<div className={`user-profile theme-${theme}`}>
<h2>{user.name}</h2>
<p>年龄: {user.age}</p>
{showEmail && user.email && <p>邮箱: {user.email}</p>}
<button onClick={() => onEdit(user.id)}>编辑</button>
</div>
);
}🎯 Props 最佳实践
1. 保持 Props 接口简洁
jsx
// ❌ 过多的 props
function ComplexComponent({
title, subtitle, content, imageUrl, imageAlt,
showImage, showSubtitle, titleColor, contentColor,
onClick, onHover, onFocus, className, style
}) {
// 复杂的组件实现
}
// ✅ 使用对象组织相关 props
function BetterComponent({
content: { title, subtitle, body },
image: { url, alt, show = true },
styling: { titleColor, contentColor, className, style },
handlers: { onClick, onHover, onFocus }
}) {
// 更清晰的组件实现
}2. 合理使用回调函数
jsx
function TodoItem({ todo, onUpdate }) {
const handleToggle = () => {
onUpdate(todo.id, { completed: !todo.completed });
};
const handleEdit = (newText) => {
onUpdate(todo.id, { text: newText });
};
return (
<div className="todo-item">
<input
type="checkbox"
checked={todo.completed}
onChange={handleToggle}
/>
<EditableText
text={todo.text}
onSave={handleEdit}
/>
</div>
);
}3. 避免 Props 突变
jsx
// ❌ 错误:修改 props
function BadComponent({ items }) {
items.push({ id: Date.now(), text: 'new item' }); // 不要这样做
return <ul>{items.map(item => <li key={item.id}>{item.text}</li>)}</ul>;
}
// ✅ 正确:创建新数组
function GoodComponent({ items, onAddItem }) {
const handleAdd = () => {
const newItem = { id: Date.now(), text: 'new item' };
onAddItem(newItem);
};
return (
<div>
<ul>
{items.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
<button onClick={handleAdd}>添加项目</button>
</div>
);
}📊 性能考虑
使用 memo 优化
jsx
import { memo } from 'react';
const ExpensiveItem = memo(function Item({ item, onSelect }) {
console.log(`渲染项目: ${item.id}`);
return (
<div onClick={() => onSelect(item.id)}>
<h3>{item.title}</h3>
<p>{item.description}</p>
</div>
);
});
// 自定义比较函数
const OptimizedItem = memo(function Item({ item, selected }) {
return (
<div className={selected ? 'selected' : ''}>
<h3>{item.title}</h3>
</div>
);
}, (prevProps, nextProps) => {
return prevProps.item.id === nextProps.item.id &&
prevProps.selected === nextProps.selected;
});📝 本章小结
Props 是 React 组件通信的核心机制,掌握 Props 的使用对于构建可维护的 React 应用至关重要。
关键要点
- ✅ Props 是只读的,用于父子组件通信
- ✅ 支持各种数据类型和函数传递
- ✅ children 是特殊的 prop
- ✅ 可以设置默认值和进行类型验证
- ✅ 合理的 props 设计提高组件可用性
最佳实践
- 保持 Props 接口简洁明了
- 使用 TypeScript 或 PropTypes 进行类型检查
- 避免过度传递 props
- 合理使用默认值
- 考虑性能影响,适当使用 memo
继续学习:下一章 - React 状态 State