列表渲染
本教程共 50 篇 · 第 11 篇 · 更新于 2026-07-29 · 约 7 分钟阅读
11. 列表渲染
本节目标:掌握用
map()渲染列表、用filter()筛选数据,理解key的重要性以及为什么不能用数组索引。
用 map() 渲染列表
map() 把一个数组转换成另一个数组。在 React里,就是把数据数组转成 JSX 数组:
const fruits = ['苹果', '香蕉', '橙子'];
const listItems = fruits.map(fruit => <li>{fruit}</li>);
// 结果:[<li>苹果</li>, <li>香蕉</li>, <li>橙子</li>]
然后在 JSX 里嵌入:
function FruitList() {
const fruits = ['苹果', '香蕉', '橙子'];
const listItems = fruits.map(fruit => <li>{fruit}</li>);
return <ul>{listItems}</ul>;
}
Note实际开发中,
map()通常直接写在 JSX 里:<ul> {fruits.map(fruit => <li>{fruit}</li>)} </ul>
key:列表项的身份证
渲染列表时,React 会报警告:Each child in a list should have a unique "key" prop。
这是因为 React 需要唯一标识每个列表项。当列表增删改时,React 靠 key 判断哪些项变了、哪些没变。
const fruits = [
{ id: 1, name: '苹果' },
{ id: 2, name: '香蕉' },
{ id: 3, name: '橙子' },
];
const listItems = fruits.map(fruit => (
<li key={fruit.id}>{fruit.name}</li>
));
key 的要求:
- 在兄弟节点之间唯一——不要求全局唯一
- 不能变——不要用
Math.random()或时间戳 - 来自数据——比如数据库 ID
为什么不能用 index 做 key
你可能会这样写:
// 不推荐
{items.map((item, index) => (
<li key={index}>{item.name}</li>
))}
React 不报错,但这是反模式。
问题在于:index 代表的是位置,不是数据本身。
假设列表是 ['A', 'B', 'C'],key 是 0, 1, 2。现在删除第一项:
- 删除前:
A(0), B(1), C(2) - 删除后:
B(0), C(1)
React 看到 key 0 还在,key 1 还在,只是值变了。它会更新这两个 DOM 节点,而不是移动它们。如果列表项有状态(比如输入框里的文字),就会错乱。
Warning只有当列表永远不会增删排序时,才能用 index 做 key。比如固定的导航菜单。其他情况一律用数据 ID。
用 filter() 筛选数据
filter() 返回满足条件的元素组成的新数组。配合 map() 可以筛选后渲染:
const people = [
{ id: 1, name: '张三', role: '前端' },
{ id: 2, name: '李四', role: '后端' },
{ id: 3, name: '王五', role: '前端' },
];
function FrontendTeam() {
const frontends = people.filter(p => p.role === '前端');
return (
<ul>
{frontends.map(p => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}
也可以链式调用:
const listItems = people
.filter(p => p.role === '前端')
.map(p => <li key={p.id}>{p.name}</li>);
key 放在哪里
key 要放在 map() 返回的最外层元素上:
// 正确
items.map(item => (
<li key={item.id}>{item.name}</li>
));
// 也正确(Fragment 需要完整写法)
items.map(item => (
<Fragment key={item.id}>
<dt>{item.name}</dt>
<dd>{item.desc}</dd>
</Fragment>
));
Note
<>...</>简写形式不能带 key。如果你需要给 Fragment 加 key,必须用<Fragment key={...}>。
列表项是组件时
如果列表项提取成了组件,key 放在组件标签上,不要放在组件内部的根元素上:
// 正确
{items.map(item => (
<TodoItem key={item.id} item={item} />
))}
// 错误:key 放在了组件内部的 <li> 上
function TodoItem({ item }) {
return <li key={item.id}>{item.name}</li>; // 不行
}
因为 React 需要 key 在数组上下文中就近可用。组件内部的 key 对 React 来说”看不见”。
实战示例:动态列表
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: '学 React', done: false },
{ id: 2, text: '写代码', done: false },
{ id: 3, text: '吃午饭', done: true },
]);
return (
<ul>
{todos
.filter(todo => !todo.done) // 只显示未完成的
.map(todo => (
<li key={todo.id}>
{todo.text}
</li>
))}
</ul>
);
}
常见错误
错误 1:忘记加 key
// 警告
{items.map(item => <li>{item.name}</li>)}
// 修复
{items.map(item => <li key={item.id}>{item.name}</li>)}
错误 2:key 不唯一
// 错误:多个项可能有相同的名字
{items.map(item => <li key={item.name}>{item.name}</li>)}
// 修复:用唯一 ID
{items.map(item => <li key={item.id}>{item.name}</li>)}
错误 3:在 map 里用 if
// 不推荐:map 里用 if 逻辑混乱
{items.map(item => {
if (item.visible) {
return <li key={item.id}>{item.name}</li>;
}
})}
// 推荐:先 filter 再 map
{items
.filter(item => item.visible)
.map(item => <li key={item.id}>{item.name}</li>)
}
本节小结
map()把数据数组转成 JSX 数组key是列表项的唯一标识,来自数据 ID- 不要用 index 做 key(除非列表永远不变)
filter()+map()实现筛选渲染- key 放在 map 返回的最外层元素上
下一节学样式方案。