渲染列表:map 转换、filter 过滤与 key 的必要性
在网页开发中,最常见的任务之一就是展示一组结构相似的数据:例如好友列表、电商商品、博客文章、聊天评论。
你肯定不希望手动机械地手写 100 遍 <li>...</li> 标签。在 React 中,我们结合 JavaScript 原生数组的 map() 和 filter() 方法,把底层的数据集合直接映射为生动的组件列表。
1. 使用 map() 将数组转换为组件列表
假设我们有一个科学家名单的字符串数组:
const people = [
'凯瑟琳·约翰逊: 数学家',
'马里奥·莫利纳: 化学家',
'穆罕默德·阿卜杜勒·萨拉姆: 物理学家',
'珀西·朱利安: 化学家',
'苏布拉马尼扬·钱德拉塞卡: 天体物理学家',
];
我们要把它渲染成一个 HTML 无序列表 <ul>:
export default function List() {
const listItems = people.map(person =>
<li>{person}</li>
);
return <ul>{listItems}</ul>;
}
JavaScript 基础补课:数组的
map()方法array.map(fn)会遍历数组中的每一项,把每一项传给函数fn计算,并将返回值按顺序组装成一个新数组。 在上面的例子中,people是一个字符串数组,经过map转换后,listItems变成了一个包含 5 个<li>...</li>JSX 元素的新数组!React 能够直接把 JSX 数组渲染到页面中。
2. 先用 filter() 过滤,再用 map() 渲染
在实际业务中,我们经常只需要展示满足特定条件的一部分项。例如,我们只想单独列出化学家:
const people = [
{ id: 0, name: '凯瑟琳·约翰逊', profession: '数学家' },
{ id: 1, name: '马里奥·莫利纳', profession: '化学家' },
{ id: 2, name: '穆罕默德·阿卜杜勒·萨拉姆', profession: '物理学家' },
{ id: 3, name: '珀西·朱利安', profession: '化学家' },
{ id: 4, name: '苏布拉马尼扬·钱德拉塞卡', profession: '天体物理学家' }
];
export default function ChemistsList() {
// 1. 过滤出化学家
const chemists = people.filter(person =>
person.profession === '化学家'
);
// 2. 将过滤后的结果映射为 JSX
const listItems = chemists.map(person =>
<li key={person.id}>
<p>
<b>{person.name}</b>: {person.profession}
</p>
</li>
);
return <ul>{listItems}</ul>;
}
3. 为什么必须为列表项提供 key?
如果你运行上面没有加 key 的第一段代码,打开浏览器的控制台,你会看到一个醒目的警告信息:
Warning: Each child in a list should have a unique "key" prop.(警告:列表中的每个子项都应该拥有一个唯一的 “key” 属性。)
什么是 key?
key 是一个特殊的字符串或数字,它就像每个人独一无二的身份证号。
想象一下这个现实生活场景: 一个班级有 10 位同学按学号坐成一排。如果老师突然让第 1 位同学离开教室,剩下的同学向前挪动一个座位:
- 如果没有名字/身份证号(没有 key):老师只看座位号(索引 0、1、2…),他会以为“原本坐第 1 个座位的同学换了张新脸,坐第 2 个座位的同学换了张新脸…”,导致误判每个人的作业本和书包;
- 如果每位同学胸前都贴着唯一姓名牌(有 key):老师一眼就能辨认出:“原来只是张三走了,李四、王五都只是换了个座位而已,他们的作业本依然属于他们自己”。
在 React 中也是一样:当列表项发生插入、删除、重排序时,key 告诉 React 哪个节点在历次渲染间是同一个实体。React 借此能够最小化 DOM 操作,并保证子组件内部的状态不会错乱!
4. key 的三大黄金准则
准则一:key 必须在兄弟节点之间保持唯一
不同的兄弟列表项不能拥有相同的 key。但是不同列表(在不同组件或不同父级)之间的 key 可以相同。
准则二:key 必须是稳定不变的
绝对不要在渲染时使用 Math.random() 临时生成 key!
如果每次渲染都生成新的随机数作为 key,React 会认为所有子项全部被销毁重建,导致列表每次打字或更新时都闪烁白屏,并且输入框的光标会不断丢失!
准则三:为什么不要使用数组索引(Index)作为 key?
如果你不写 key,React 默认会使用数组的下标索引(0, 1, 2…)作为 key。 当列表仅仅是静态展示、永远不增删、永远不改变顺序时,使用 index 是勉强可行的。
但如果列表项支持删除、置顶或重新排序,使用 index 会导致灾难性 bug: 假设你在第 0 项输入框里写了“买牛奶”,然后点击删除了第 0 项。 原本的第 1 项(“做作业”)现在的下标变成了 0!React 会误认为“第 0 项还在,只是文本变了”,结果你输入框里的未保存草稿“买牛奶”竟然出现在了“做作业”的格子里!
从哪里获取稳定的 key?
- 来自数据库或后端接口的唯一标识符(如
product.id);- 本地新增数据时,使用自增计数器或
crypto.randomUUID()生成唯一 ID 并固化在数据对象中。
总结
- 使用
array.map()把数据对象数组转化为 JSX 元素数组; - 使用
array.filter()过滤出所需子集; - 列表项的外层元素必须指定稳定的
key属性; - 好的 key 来源于数据本身的 ID,切忌使用随机数或可变索引 index。