第 147 章:条件渲染与列表渲染
学习目标
- 掌握 4 种条件渲染写法
- 理解列表 key 的作用
- 学会稳定 ID 与 key 的选择
- 处理空状态和加载状态
一、条件渲染 4 种方式
1.1 if 语句(在 return 之前)
tsx
function Greeting({ user }: { user: User | null }) {
if (!user) {
return <p>请登录</p>;
}
return <p>欢迎,{user.name}!</p>;
}1.2 三元运算符
tsx
{isLoggedIn ? <UserMenu /> : <LoginButton />}
{user ? <Avatar src={user.avatar} /> : <DefaultAvatar />}1.3 逻辑与 &&
tsx
{isAdmin && <AdminPanel />}
{error && <ErrorMessage msg={error} />}
{count > 0 && <Badge count={count} />}1.4 IIFE / 立即函数
tsx
{(() => {
switch (status) {
case 'loading': return <Spinner />;
case 'error': return <Error />;
case 'success': return <Content data={data} />;
default: return null;
}
})()}二、列表渲染
2.1 基础 map
tsx
const fruits = ['苹果', '香蕉', '橙子'];
return (
<ul>
{fruits.map((fruit, index) => (
<li key={fruit}>{fruit}</li>
))}
</ul>
);2.2 对象列表
tsx
interface User { id: number; name: string; email: string; }
const users: User[] = [
{ id: 1, name: 'Tom', email: 'tom@x.com' },
{ id: 2, name: 'Jerry', email: 'jerry@x.com' },
];
return (
<ul>
{users.map(user => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
);2.3 表格
tsx
<table>
<thead>
<tr><th>ID</th><th>姓名</th><th>邮箱</th></tr>
</thead>
<tbody>
{users.map(user => (
<tr key={user.id}>
<td>{user.id}</td>
<td>{user.name}</td>
<td>{user.email}</td>
</tr>
))}
</tbody>
</table>三、Key 的选择
3.1 为什么需要 key
React 用 key 识别哪些元素变了、新增了、删除了,帮助高效 diff。
tsx
// ❌ 没 key,React 警告
{items.map(item => <li>{item}</li>)}
// ✅ 有 key
{items.map(item => <li key={item.id}>{item.name}</li>)}3.2 稳定的 ID
tsx
// ✅ 最佳:用业务 ID
items.map(item => <li key={item.id}>...</li>)
// ⚠️ 可接受:用 index(只在静态列表)
items.map((item, i) => <li key={i}>...</li>)
// ❌ 错误:用随机数
items.map(item => <li key={Math.random()}>...</li>)3.3 列表会重排时不要用 index
tsx
// ❌ 重排后 key 不变,React 错乱
const [list, setList] = useState(['A', 'B', 'C']);
// 删除 A 后,key=0 实际是 B,出错
{list.map((item, i) => <li key={i}>{item}</li>)}四、空状态与加载状态
tsx
function UserList() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
if (users.length === 0) return <Empty message="暂无用户" />;
return (
<ul>
{users.map(user => <UserItem key={user.id} user={user} />)}
</ul>
);
}五、过滤与排序
tsx
function UserList({ users }: { users: User[] }) {
const [keyword, setKeyword] = useState('');
const filtered = users.filter(u =>
u.name.toLowerCase().includes(keyword.toLowerCase())
);
return (
<>
<input
placeholder="搜索"
value={keyword}
onChange={e => setKeyword(e.target.value)}
/>
<ul>
{filtered.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</>
);
}六、列表项组件
tsx
// 列表项单独封装
function TodoItem({ todo, onToggle, onDelete }: TodoItemProps) {
return (
<li className="flex items-center gap-2">
<input
type="checkbox"
checked={todo.done}
onChange={() => onToggle(todo.id)}
/>
<span className={todo.done ? 'line-through' : ''}>
{todo.text}
</span>
<button onClick={() => onDelete(todo.id)}>删除</button>
</li>
);
}
// 列表
function TodoList({ todos, onToggle, onDelete }: TodoListProps) {
return (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={onToggle}
onDelete={onDelete}
/>
))}
</ul>
);
}七、嵌套列表
tsx
interface Category { id: number; name: string; items: Item[]; }
{categories.map(cat => (
<div key={cat.id}>
<h3>{cat.name}</h3>
<ul>
{cat.items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
))}八、本章小结
| 概念 | 关键 |
|---|---|
| 条件渲染 | if / 三元 / && / IIFE |
| 列表 | map + 稳定 key |
| key 选 ID | 不要用 index 或随机数 |
| 状态分支 | loading / error / empty / success |
动手练习
- 写一个待办列表,支持添加、勾选、删除、过滤(全部/已完成/未完成)
- 写一个用户列表,支持搜索、排序、加载状态
- 写一个评论树,嵌套渲染回复
推荐阅读
下一章:第 148 章:事件处理 →