React 属性(Props)语法知识点与案例详解
一、Props 基础概念
Props(Properties 的缩写)是 React 组件接收外部数据的主要方式。它们是从父组件传递给子组件的只读数据,用于配置组件的行为和显示内容。
二、Props 核心语法知识点
1. Props 基本传递
jsx
// 父组件传递 props
<ChildComponent name="张三" age={25} />
// 子组件接收 props
function ChildComponent(props) {
return <div>{props.name} - {props.age}岁</div>
}
2. Props 解构赋值(推荐)
jsx
// 函数组件中解构 props
function ChildComponent({ name, age, children }) {
return (
<div>
<p>姓名: {name}</p>
<p>年龄: {age}</p>
{children}
</div>
)
}
// 或者在函数体内解构
function ChildComponent(props) {
const { name, age } = props;
return <div>{name} - {age}</div>
}
3. Props 默认值
jsx
// 函数组件设置默认 props
function UserProfile({ name = "匿名用户", age = 0, email = "未提供" }) {
return (
<div>
<p>姓名: {name}</p>
<p>年龄: {age}</p>
<p>邮箱: {email}</p>
</div>
)
}
// 使用 defaultProps(适用于类组件或函数组件)
UserProfile.defaultProps = {
name: "匿名用户",
age: 0,
email: "未提供邮箱"
};
4. Props 类型检查(PropTypes)
jsx
import PropTypes from 'prop-types';
function UserCard({ name, age, isActive, hobbies, onButtonClick }) {
return (
<div>
<h3>{name} ({age}岁)</h3>
<p>状态: {isActive ? '活跃' : '非活跃'}</p>
<button onClick={onButtonClick}>点击我</button>
</div>
)
}
// 定义 prop 类型
UserCard.propTypes = {
name: PropTypes.string.isRequired, // 必填字符串
age: PropTypes.number, // 数字
isActive: PropTypes.bool, // 布尔值
hobbies: PropTypes.arrayOf(PropTypes.string), // 字符串数组
onButtonClick: PropTypes.func // 函数
};
// 设置默认值
UserCard.defaultProps = {
age: 18,
isActive: true,
hobbies: [],
onButtonClick: () => console.log('按钮被点击')
};
5. 传递对象作为 Props
jsx
// 传递对象
const userInfo = {
name: "李四",
age: 30,
email: "lisi@example.com"
};
<UserProfile user={userInfo} />
// 子组件接收
function UserProfile({ user }) {
return (
<div>
<p>姓名: {user.name}</p>
<p>年龄: {user.age}</p>
<p>邮箱: {user.email}</p>
</div>
)
}
// 或者使用展开运算符
<UserProfile {...userInfo} />
// 子组件
function UserProfile({ name, age, email }) {
return (
<div>
<p>姓名: {name}</p>
<p>年龄: {age}</p>
<p>邮箱: {email}</p>
</div>
)
}
6. Children Props
jsx
// 父组件传递子元素
<Container>
<h1>这是标题</h1>
<p>这是段落内容</p>
<button>按钮</button>
</Container>
// 子组件接收 children
function Container({ children, style }) {
return (
<div style={{ padding: '20px', border: '1px solid #ccc', ...style }}>
{children} {/* 渲染所有子元素 */}
</div>
)
}
7. 函数作为 Props(回调函数)
jsx
// 父组件
function ParentComponent() {
const handleUserClick = (userId, userName) => {
console.log(`用户 ${userName} (ID: ${userId}) 被点击`);
alert(`你好,${userName}!`);
};
return (
<UserList onItemClick={handleUserClick} />
)
}
// 子组件
function UserList({ onItemClick }) {
const users = [
{ id: 1, name: "张三" },
{ id: 2, name: "李四" },
{ id: 3, name: "王五" }
];
return (
<div>
{users.map(user => (
<div
key={user.id}
onClick={() => onItemClick(user.id, user.name)}
style={{ cursor: 'pointer', padding: '10px', margin: '5px', border: '1px solid #ddd' }}
>
{user.name}
</div>
))}
</div>
)
}
8. 数组作为 Props
jsx
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo, index) => (
<li key={index}>
<input type="checkbox" defaultChecked={todo.completed} />
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
</span>
</li>
))}
</ul>
)
}
// 使用组件
const todoItems = [
{ text: "学习 React", completed: false },
{ text: "完成项目", completed: true },
{ text: "阅读文档", completed: false }
];
<TodoList todos={todoItems} />
9. 条件 Props
jsx
function ConditionalComponent({ showTitle, title, content }) {
return (
<div>
{/* 条件渲染 title */}
{showTitle && <h2>{title}</h2>}
<p>{content}</p>
</div>
)
}
// 使用
<ConditionalComponent
showTitle={true}
title="条件标题"
content="这是内容"
/>
10. Spread Operator 传递 Props
jsx
function UserProfile({ name, age, city, country, ...otherProps }) {
return (
<div {...otherProps}> {/* 将剩余的 props 传递给 div */}
<h3>{name}</h3>
<p>年龄: {age}</p>
<p>城市: {city}, 国家: {country}</p>
</div>
)
}
// 使用
const userProps = {
name: "赵六",
age: 28,
city: "北京",
country: "中国",
style: { backgroundColor: '#f0f0f0', padding: '20px' },
className: "user-profile"
};
<UserProfile {...userProps} />
三、完整案例:用户管理系统
jsx
import React from 'react';
import PropTypes from 'prop-types';
// 1. 用户卡片组件
function UserCard({
user,
onEdit,
onDelete,
isSelected = false,
className = ""
}) {
// 解构用户对象
const { id, name, email, age, avatar } = user;
return (
<div
className={`user-card ${isSelected ? 'selected' : ''} ${className}`}
style={{
border: '1px solid #ddd',
borderRadius: '8px',
padding: '15px',
margin: '10px 0',
backgroundColor: isSelected ? '#e3f2fd' : '#fff',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
cursor: 'pointer'
}}
>
<div style={{ display: 'flex', alignItems: 'center' }}>
{/* 用户头像 */}
<img
src={avatar || 'https://via.placeholder.com/50'}
alt={name}
style={{
width: '50px',
height: '50px',
borderRadius: '50%',
marginRight: '15px'
}}
/>
{/* 用户信息 */}
<div style={{ flex: 1 }}>
<h3 style={{ margin: '0 0 5px 0', color: '#333' }}>{name}</h3>
<p style={{ margin: '0 0 5px 0', fontSize: '14px', color: '#666' }}>
{email}
</p>
<p style={{ margin: '0', fontSize: '14px', color: '#666' }}>
年龄: {age}岁
</p>
</div>
{/* 操作按钮 */}
<div>
<button
onClick={(e) => {
e.stopPropagation(); // 阻止事件冒泡
onEdit(id);
}}
style={{
marginRight: '5px',
padding: '5px 10px',
backgroundColor: '#007bff',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
编辑
</button>
<button
onClick={(e) => {
e.stopPropagation();
onDelete(id);
}}
style={{
padding: '5px 10px',
backgroundColor: '#dc3545',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
删除
</button>
</div>
</div>
</div>
);
}
// 定义 UserCard 的 prop 类型
UserCard.propTypes = {
user: PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
email: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
avatar: PropTypes.string
}).isRequired,
onEdit: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
isSelected: PropTypes.bool,
className: PropTypes.string
};
// 2. 用户列表组件
function UserList({
users,
onUserEdit,
onUserDelete,
selectedUserId,
onUserSelect
}) {
return (
<div>
<h2>用户列表 ({users.length} 位用户)</h2>
{users.length === 0 ? (
<p style={{ textAlign: 'center', color: '#666' }}>暂无用户数据</p>
) : (
users.map(user => (
<UserCard
key={user.id}
user={user}
onEdit={onUserEdit}
onDelete={onUserDelete}
isSelected={selectedUserId === user.id}
onClick={() => onUserSelect(user.id)}
/>
))
)}
</div>
);
}
// 定义 UserList 的 prop 类型
UserList.propTypes = {
users: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
email: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
avatar: PropTypes.string
})
).isRequired,
onUserEdit: PropTypes.func.isRequired,
onUserDelete: PropTypes.func.isRequired,
selectedUserId: PropTypes.number,
onUserSelect: PropTypes.func.isRequired
};
// 3. 用户统计组件
function UserStats({ users, filterByAge }) {
// 计算统计数据
const totalUsers = users.length;
const averageAge = totalUsers > 0
? (users.reduce((sum, user) => sum + user.age, 0) / totalUsers).toFixed(1)
: 0;
const activeUsers = users.filter(user => user.age >= filterByAge).length;
return (
<div style={{
backgroundColor: '#f8f9fa',
padding: '15px',
borderRadius: '8px',
marginBottom: '20px',
border: '1px solid #e9ecef'
}}>
<h3>用户统计</h3>
<p>总用户数: <strong>{totalUsers}</strong></p>
<p>平均年龄: <strong>{averageAge}</strong> 岁</p>
<p>年龄 ≥ {filterByAge} 岁的用户: <strong>{activeUsers}</strong> 位</p>
</div>
);
}
// 定义 UserStats 的 prop 类型
UserStats.propTypes = {
users: PropTypes.array.isRequired,
filterByAge: PropTypes.number
};
UserStats.defaultProps = {
filterByAge: 18
};
// 4. 主应用组件
function App() {
// 模拟用户数据
const [users, setUsers] = React.useState([
{
id: 1,
name: "张三",
email: "zhangsan@example.com",
age: 25,
avatar: "https://via.placeholder.com/50/007bff/ffffff?text=ZS"
},
{
id: 2,
name: "李四",
email: "lisi@example.com",
age: 32,
avatar: "https://via.placeholder.com/50/28a745/ffffff?text=LS"
},
{
id: 3,
name: "王五",
email: "wangwu@example.com",
age: 28,
avatar: "https://via.placeholder.com/50/dc3545/ffffff?text=WW"
}
]);
// 选中的用户ID
const [selectedUserId, setSelectedUserId] = React.useState(null);
// 编辑用户
const handleEditUser = (userId) => {
alert(`编辑用户 ID: ${userId}`);
// 这里可以打开编辑模态框或跳转到编辑页面
};
// 删除用户
const handleDeleteUser = (userId) => {
if (window.confirm('确定要删除这个用户吗?')) {
setUsers(users.filter(user => user.id !== userId));
if (selectedUserId === userId) {
setSelectedUserId(null);
}
}
};
// 选择用户
const handleSelectUser = (userId) => {
setSelectedUserId(userId);
};
// 添加示例用户
const handleAddSampleUser = () => {
const newId = users.length > 0 ? Math.max(...users.map(u => u.id)) + 1 : 1;
const newUser = {
id: newId,
name: `用户${newId}`,
email: `user${newId}@example.com`,
age: Math.floor(Math.random() * 30) + 18, // 18-47岁随机年龄
avatar: `https://via.placeholder.com/50/${Math.floor(Math.random()*16777215).toString(16)}/ffffff?text=U${newId}`
};
setUsers([...users, newUser]);
};
return (
<div style={{ maxWidth: '800px', margin: '0 auto', padding: '20px' }}>
<h1>React Props 详细案例 - 用户管理系统</h1>
{/* 用户统计 */}
<UserStats users={users} filterByAge={25} />
{/* 添加用户按钮 */}
<button
onClick={handleAddSampleUser}
style={{
marginBottom: '20px',
padding: '10px 20px',
backgroundColor: '#28a745',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '16px'
}}
>
添加示例用户
</button>
{/* 用户列表 */}
<UserList
users={users}
onUserEdit={handleEditUser}
onUserDelete={handleDeleteUser}
selectedUserId={selectedUserId}
onUserSelect={handleSelectUser}
/>
{/* 选中用户信息 */}
{selectedUserId && (
<div style={{
marginTop: '20px',
padding: '15px',
backgroundColor: '#e3f2fd',
border: '1px solid #2196f3',
borderRadius: '8px'
}}>
<h3>当前选中的用户: {users.find(u => u.id === selectedUserId)?.name}</h3>
</div>
)}
</div>
);
}
// 导出组件
export default App;
export { UserCard, UserList, UserStats };
四、Props 使用注意事项
1. Props 是只读的
jsx
// ❌ 错误:不能修改 props
function BadComponent(props) {
props.name = "新名字"; // 不要这样做!
return <div>{props.name}</div>;
}
// ✅ 正确:使用状态或创建新变量
function GoodComponent({ name }) {
const [localName, setLocalName] = useState(name);
return <div>{localName}</div>;
}
2. 避免传递过多 Props(Prop Drilling)
jsx
// ❌ 不好的做法:层层传递 props
<GrandParent user={user}>
<Parent user={user}>
<Child user={user}>
<GrandChild user={user} />
</Child>
</Parent>
</GrandParent>
// ✅ 更好的做法:使用 Context API 或状态管理库
3. 性能优化:避免不必要的重新渲染
jsx
// 使用 React.memo 避免不必要的重新渲染
const MemoizedUserCard = React.memo(UserCard);
// 使用 useCallback 避免函数重新创建
function ParentComponent() {
const handleEdit = useCallback((id) => {
console.log('编辑用户', id);
}, []); // 依赖数组为空,函数不会重新创建
return <UserCard onEdit={handleEdit} />;
}
五、总结
Props 是 React 组件间通信的基础,掌握以下要点:
- 数据流向:单向数据流,从父组件到子组件
- 只读性:Props 是只读的,不能在子组件中修改
- 灵活性:可以传递任何类型的数据(字符串、数字、数组、对象、函数等)
- 类型安全:使用 PropTypes 进行类型检查
- 默认值:为 Props 设置合理的默认值
- 解构赋值:推荐使用解构赋值提高代码可读性
- Children:特殊 prop,用于传递子元素
- 性能考虑:避免不必要的重新渲染