React 条件和列表渲染
概述
在 React 中,我们经常需要根据不同的条件显示不同的内容,或者渲染动态的列表数据。本章将学习如何在 React 中进行条件渲染和列表渲染,掌握各种渲染技巧和最佳实践。
🔀 条件渲染
基础条件渲染
jsx
function BasicConditionalRendering() {
const [isLoggedIn, setIsLoggedIn] = React.useState(false);
const [user, setUser] = React.useState(null);
const login = () => {
setUser({ name: '张三', role: 'admin' });
setIsLoggedIn(true);
};
const logout = () => {
setUser(null);
setIsLoggedIn(false);
};
// 方式1:使用 if 语句
if (!isLoggedIn) {
return (
<div>
<h2>请登录</h2>
<button onClick={login}>登录</button>
</div>
);
}
return (
<div>
<h2>欢迎,{user.name}!</h2>
<button onClick={logout}>登出</button>
</div>
);
}三元运算符
jsx
function TernaryOperator() {
const [loading, setLoading] = React.useState(false);
const [data, setData] = React.useState(null);
const [error, setError] = React.useState(null);
const fetchData = async () => {
setLoading(true);
setError(null);
try {
// 模拟 API 调用
await new Promise(resolve => setTimeout(resolve, 2000));
setData({ message: '数据加载成功!' });
} catch (err) {
setError('加载失败');
} finally {
setLoading(false);
}
};
return (
<div>
<h2>数据获取示例</h2>
<button onClick={fetchData} disabled={loading}>
{loading ? '加载中...' : '获取数据'}
</button>
{/* 三元运算符嵌套 */}
{loading ? (
<div>正在加载数据...</div>
) : error ? (
<div style={{ color: 'red' }}>错误: {error}</div>
) : data ? (
<div style={{ color: 'green' }}>{data.message}</div>
) : (
<div>点击按钮获取数据</div>
)}
</div>
);
}逻辑与运算符
jsx
function LogicalAndOperator() {
const [showWarning, setShowWarning] = React.useState(false);
const [notifications, setNotifications] = React.useState([]);
const [userRole, setUserRole] = React.useState('user');
const addNotification = () => {
const newNotification = {
id: Date.now(),
message: `通知 ${notifications.length + 1}`,
timestamp: new Date().toLocaleTimeString()
};
setNotifications([...notifications, newNotification]);
};
return (
<div>
<h2>条件显示组件</h2>
<div>
<button onClick={() => setShowWarning(!showWarning)}>
切换警告显示
</button>
<button onClick={addNotification}>添加通知</button>
<button onClick={() => setUserRole(userRole === 'admin' ? 'user' : 'admin')}>
切换用户角色: {userRole}
</button>
</div>
{/* 使用 && 运算符 */}
{showWarning && (
<div style={{ backgroundColor: '#fff3cd', padding: '10px', margin: '10px 0' }}>
⚠️ 这是一个警告消息
</div>
)}
{/* 条件显示通知 */}
{notifications.length > 0 && (
<div>
<h3>通知列表 ({notifications.length})</h3>
{notifications.map(notification => (
<div key={notification.id} style={{ padding: '5px', border: '1px solid #ddd' }}>
[{notification.timestamp}] {notification.message}
</div>
))}
</div>
)}
{/* 管理员专用功能 */}
{userRole === 'admin' && (
<div style={{ backgroundColor: '#d4edda', padding: '10px', margin: '10px 0' }}>
<h3>管理员面板</h3>
<button>用户管理</button>
<button>系统设置</button>
</div>
)}
</div>
);
}Switch 语句模拟
jsx
function SwitchLikeRendering() {
const [status, setStatus] = React.useState('loading');
const [userType, setUserType] = React.useState('guest');
// 使用对象映射模拟 switch
const statusComponents = {
loading: <div>⏳ 加载中...</div>,
success: <div style={{ color: 'green' }}>✅ 操作成功</div>,
error: <div style={{ color: 'red' }}>❌ 操作失败</div>,
idle: <div>等待操作</div>
};
// 使用函数模拟 switch
const renderUserInterface = () => {
switch (userType) {
case 'admin':
return (
<div style={{ backgroundColor: '#e7f3ff' }}>
<h3>管理员界面</h3>
<button>用户管理</button>
<button>系统设置</button>
<button>数据统计</button>
</div>
);
case 'vip':
return (
<div style={{ backgroundColor: '#fff7e6' }}>
<h3>VIP 用户界面</h3>
<button>专属服务</button>
<button>高级功能</button>
</div>
);
case 'user':
return (
<div style={{ backgroundColor: '#f6fff7' }}>
<h3>普通用户界面</h3>
<button>基础功能</button>
</div>
);
default:
return (
<div>
<h3>访客界面</h3>
<button>注册</button>
<button>登录</button>
</div>
);
}
};
return (
<div>
<h2>复杂条件渲染</h2>
<div>
<label>状态: </label>
<select value={status} onChange={(e) => setStatus(e.target.value)}>
<option value="loading">加载中</option>
<option value="success">成功</option>
<option value="error">错误</option>
<option value="idle">空闲</option>
</select>
</div>
<div>
<label>用户类型: </label>
<select value={userType} onChange={(e) => setUserType(e.target.value)}>
<option value="guest">访客</option>
<option value="user">用户</option>
<option value="vip">VIP</option>
<option value="admin">管理员</option>
</select>
</div>
<div style={{ margin: '20px 0' }}>
<h3>当前状态:</h3>
{statusComponents[status]}
</div>
<div>
{renderUserInterface()}
</div>
</div>
);
}📋 列表渲染
基础列表渲染
jsx
function BasicListRendering() {
const [items, setItems] = React.useState([
{ id: 1, name: '苹果', price: 5.99, category: '水果' },
{ id: 2, name: '香蕉', price: 3.99, category: '水果' },
{ id: 3, name: '牛奶', price: 12.99, category: '乳制品' },
{ id: 4, name: '面包', price: 8.99, category: '主食' }
]);
const [filter, setFilter] = React.useState('all');
const filteredItems = items.filter(item =>
filter === 'all' || item.category === filter
);
const categories = ['all', ...new Set(items.map(item => item.category))];
return (
<div>
<h2>商品列表</h2>
{/* 过滤器 */}
<div style={{ marginBottom: '20px' }}>
{categories.map(category => (
<button
key={category}
onClick={() => setFilter(category)}
style={{
margin: '0 5px',
backgroundColor: filter === category ? '#007bff' : '#f8f9fa',
color: filter === category ? 'white' : 'black'
}}
>
{category === 'all' ? '全部' : category}
</button>
))}
</div>
{/* 商品列表 */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '10px' }}>
{filteredItems.map(item => (
<div
key={item.id}
style={{
border: '1px solid #ddd',
borderRadius: '5px',
padding: '15px',
backgroundColor: '#f8f9fa'
}}
>
<h3>{item.name}</h3>
<p>分类: {item.category}</p>
<p style={{ color: '#007bff', fontSize: '18px', fontWeight: 'bold' }}>
¥{item.price}
</p>
</div>
))}
</div>
{filteredItems.length === 0 && (
<div style={{ textAlign: 'center', padding: '40px', color: '#666' }}>
没有找到符合条件的商品
</div>
)}
</div>
);
}动态列表操作
jsx
function DynamicListOperations() {
const [todos, setTodos] = React.useState([
{ id: 1, text: '学习 React', completed: false, priority: 'high' },
{ id: 2, text: '完成项目', completed: true, priority: 'medium' },
{ id: 3, text: '写文档', completed: false, priority: 'low' }
]);
const [newTodo, setNewTodo] = React.useState('');
const [sortBy, setSortBy] = React.useState('id');
const addTodo = () => {
if (newTodo.trim()) {
const todo = {
id: Date.now(),
text: newTodo,
completed: false,
priority: 'medium'
};
setTodos([...todos, todo]);
setNewTodo('');
}
};
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
const changePriority = (id, priority) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, priority } : todo
));
};
// 排序逻辑
const sortedTodos = [...todos].sort((a, b) => {
switch (sortBy) {
case 'text':
return a.text.localeCompare(b.text);
case 'completed':
return a.completed - b.completed;
case 'priority':
const priorityOrder = { high: 3, medium: 2, low: 1 };
return priorityOrder[b.priority] - priorityOrder[a.priority];
default:
return a.id - b.id;
}
});
const getPriorityColor = (priority) => {
switch (priority) {
case 'high': return '#dc3545';
case 'medium': return '#ffc107';
case 'low': return '#28a745';
default: return '#6c757d';
}
};
return (
<div>
<h2>待办事项管理</h2>
{/* 添加新任务 */}
<div style={{ marginBottom: '20px' }}>
<input
type="text"
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addTodo()}
placeholder="输入新任务..."
style={{ padding: '8px', marginRight: '10px', width: '200px' }}
/>
<button onClick={addTodo}>添加</button>
</div>
{/* 排序选项 */}
<div style={{ marginBottom: '20px' }}>
<label>排序方式: </label>
<select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
<option value="id">默认</option>
<option value="text">按名称</option>
<option value="completed">按状态</option>
<option value="priority">按优先级</option>
</select>
</div>
{/* 任务列表 */}
<div>
{sortedTodos.map(todo => (
<div
key={todo.id}
style={{
display: 'flex',
alignItems: 'center',
padding: '10px',
margin: '5px 0',
border: '1px solid #ddd',
borderRadius: '5px',
backgroundColor: todo.completed ? '#f8f9fa' : 'white'
}}
>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
style={{ marginRight: '10px' }}
/>
<span
style={{
flex: 1,
textDecoration: todo.completed ? 'line-through' : 'none',
color: todo.completed ? '#6c757d' : 'black'
}}
>
{todo.text}
</span>
<select
value={todo.priority}
onChange={(e) => changePriority(todo.id, e.target.value)}
style={{
marginRight: '10px',
color: getPriorityColor(todo.priority)
}}
>
<option value="low">低</option>
<option value="medium">中</option>
<option value="high">高</option>
</select>
<button
onClick={() => deleteTodo(todo.id)}
style={{ color: 'red', background: 'none', border: 'none' }}
>
删除
</button>
</div>
))}
</div>
{todos.length === 0 && (
<div style={{ textAlign: 'center', padding: '40px', color: '#666' }}>
暂无任务,添加一个开始吧!
</div>
)}
{/* 统计信息 */}
<div style={{ marginTop: '20px', padding: '10px', backgroundColor: '#f8f9fa' }}>
<p>总任务: {todos.length}</p>
<p>已完成: {todos.filter(t => t.completed).length}</p>
<p>未完成: {todos.filter(t => !t.completed).length}</p>
</div>
</div>
);
}Key 属性的重要性
jsx
function KeyImportance() {
const [items, setItems] = React.useState([
{ id: 'a', name: '项目 A', count: 0 },
{ id: 'b', name: '项目 B', count: 0 },
{ id: 'c', name: '项目 C', count: 0 }
]);
const addItemAtStart = () => {
const newItem = {
id: Math.random().toString(36).substr(2, 9),
name: `项目 ${String.fromCharCode(65 + items.length)}`,
count: 0
};
setItems([newItem, ...items]);
};
const updateCount = (id, delta) => {
setItems(items.map(item =>
item.id === id ? { ...item, count: item.count + delta } : item
));
};
const removeItem = (id) => {
setItems(items.filter(item => item.id !== id));
};
return (
<div>
<h2>Key 属性演示</h2>
<button onClick={addItemAtStart} style={{ marginBottom: '20px' }}>
在开头添加项目
</button>
<div style={{ display: 'flex', gap: '20px' }}>
{/* 正确使用 key */}
<div>
<h3>✅ 正确:使用唯一 ID 作为 key</h3>
{items.map(item => (
<div key={item.id} style={{ border: '1px solid #ddd', padding: '10px', margin: '5px 0' }}>
<div>{item.name}</div>
<div>
<input
type="number"
value={item.count}
onChange={(e) => updateCount(item.id, parseInt(e.target.value) - item.count)}
style={{ width: '60px', marginRight: '10px' }}
/>
<button onClick={() => updateCount(item.id, 1)}>+</button>
<button onClick={() => updateCount(item.id, -1)}>-</button>
<button onClick={() => removeItem(item.id)} style={{ marginLeft: '10px', color: 'red' }}>
删除
</button>
</div>
</div>
))}
</div>
{/* 错误使用 key */}
<div>
<h3>❌ 错误:使用索引作为 key</h3>
{items.map((item, index) => (
<div key={index} style={{ border: '1px solid #ddd', padding: '10px', margin: '5px 0' }}>
<div>{item.name}</div>
<div>
<input
type="number"
value={item.count}
onChange={(e) => updateCount(item.id, parseInt(e.target.value) - item.count)}
style={{ width: '60px', marginRight: '10px' }}
/>
<button onClick={() => updateCount(item.id, 1)}>+</button>
<button onClick={() => updateCount(item.id, -1)}>-</button>
<button onClick={() => removeItem(item.id)} style={{ marginLeft: '10px', color: 'red' }}>
删除
</button>
</div>
</div>
))}
</div>
</div>
<div style={{ marginTop: '20px', padding: '10px', backgroundColor: '#fff3cd' }}>
<strong>注意:</strong> 在左侧输入一些数字,然后点击"在开头添加项目",
观察两列的输入值变化。使用索引作为 key 会导致输入状态错乱。
</div>
</div>
);
}🎯 高级渲染技巧
渲染优化
jsx
const MemoizedItem = React.memo(function Item({ item, onUpdate, onDelete }) {
console.log('渲染:', item.name);
return (
<div style={{ padding: '10px', border: '1px solid #ddd', margin: '5px 0' }}>
<span>{item.name} - {item.value}</span>
<button onClick={() => onUpdate(item.id)}>更新</button>
<button onClick={() => onDelete(item.id)}>删除</button>
</div>
);
});
function OptimizedRendering() {
const [items, setItems] = React.useState([
{ id: 1, name: '项目 1', value: 0 },
{ id: 2, name: '项目 2', value: 0 },
{ id: 3, name: '项目 3', value: 0 }
]);
const [counter, setCounter] = React.useState(0);
const updateItem = React.useCallback((id) => {
setItems(items => items.map(item =>
item.id === id ? { ...item, value: item.value + 1 } : item
));
}, []);
const deleteItem = React.useCallback((id) => {
setItems(items => items.filter(item => item.id !== id));
}, []);
return (
<div>
<h2>渲染优化演示</h2>
<p>计数器: {counter}</p>
<button onClick={() => setCounter(counter + 1)}>
增加计数器(不影响列表项渲染)
</button>
<div>
{items.map(item => (
<MemoizedItem
key={item.id}
item={item}
onUpdate={updateItem}
onDelete={deleteItem}
/>
))}
</div>
</div>
);
}📝 本章小结
通过本章学习,你应该掌握了:
条件渲染技巧
- ✅ if 语句、三元运算符、逻辑与运算符
- ✅ 复杂条件的处理策略
- ✅ 状态驱动的 UI 变化
- ✅ 错误边界和加载状态
列表渲染技巧
- ✅ map() 方法渲染数组
- ✅ Key 属性的正确使用
- ✅ 动态列表的增删改查
- ✅ 列表排序和过滤
最佳实践
- Key 的重要性:使用唯一且稳定的标识符
- 性能优化:使用 React.memo 避免不必要的渲染
- 条件复杂性:合理拆分复杂的条件逻辑
- 状态管理:保持状态结构简单清晰
- 用户体验:提供加载状态和空状态提示
继续学习:下一章 - React 生命周期