思考一个场景:用 React 写一个页面,实现具有增删功能 TodoList 的列表。
从逻辑上讲,这道题非常简单。
首先,使用一个名为 list
的数组来记录所有的 TodoList, 然后实现两个函数:
- 一个函数名为
addItem(value: string) => void
, 它负责将名为value
的事件加到list
中; - 一个函数名为
deleteItem(index: number) => void
, 它负责将下标为index
的事件从list
中删除。
之后,可以书写以下的代码来实现 React 中列表的渲染:
const ListShow = (props) => {
const {list, deleteItem} = props;
return (
<ul>
{list.map((value, index) => (
<li>
<span>{value}</span>
<button
onClick={() => {
deleteItem(index);
}}
>
Delete
</button>
</li>
))}
</ul>
)
}
但是,对于上述代码,在 <li>
一行中,我们缺少了重要的一个值:key
, 由此,导致了 React 的 Warning: Warning: Each child in a list should have a unique "key" prop.
在 React 中,对于列表的渲染,需要为列表中(而不是全局)的每一项赋予不同的 key
值, 以帮助 React 识别哪些元素被改变。
我们把代码写成如下:
const ListShow = (props) => {
const {list, deleteItem} = props;
return (
<ul>
{list.map((value, index) => (
<li key={value}>
<span>{value}</span>
<button
onClick={() => {
deleteItem(index);
}}
>
Delete
</button>
</li>
))}
</ul>
)
}
注意,唯一的不同是将 <li>
更改为 <li key={value}>
, 这也埋下了坑点:当我们添加相同 todo, 会产生相同的 key 值,这时会造成非常严重的渲染问题,例如,当 list
中的内容为 ['1','2','3','1','2','3']
, 再进行添加和删除时,结果如下:
不去深究 React 源码的执行机制,但要记住:React 中任何列表的渲染,key 都应该保持唯一。
既然要求 key 值不唯一,那么自然就想到用索引来充当 key
值。
首先,来看索引作为 key
的代码:
const ListShow = (props) => {
const {list, deleteItem} = props;
return (
<ul>
{list.map((value, index) => (
<li key={index}>
<span>{value}</span>
<button
onClick={() => {
deleteItem(index);
}}
>
Delete
</button>
</li>
))}
</ul>
)
}
其效果如下:
可见,TodoList 可以符合预期的运行。
但是,React 官网并不建议用 index
作为 key 值,因为每次增加或删除操作,都会导致 list
中的 item
的索引的变化,例如 ['1','2','3']
对应的索引为 [0,1,2]
, 执行 deleteItem(1)
后结果就变为 ['1', '3']
, 索引为 [0,1]
. 这会导致 diff 算法重新遍历一边来进行比较。
另外,将 index
作为 key 值也可能会导致渲染问题,可见:https://jsbin.com/wohima/edit?output.
其实,很多列表展示的是从数据库拿到的数据,而数据库表的主键 id 是唯一的,因此,建议使用类似于这种的 id
作为 key
值。
看到这,有人就会有疑问,这不是 React 官网早已叙述过的内容吗,为什么还值得写出来呢?
原因来自于 antd3 中的一个坑点:通过 react devtools 可以看到,其 key
值为其 value
.
antd 中 select 复用了 rc-select 组件,但在 rc-select 中是设计如此,还是一个 bug, 便不得而知。
综上,我认为,在使用 react 列表中,应该:
- 任何情况下都不要使用重复的
key
值。 - 如果不需要考虑列表的增加/删除/变更等操作,则可以使用索引
index
, 但并不建议。 - 建议使用唯一不可变的标识
id
.
关于重复 key 和以索引为 key 可见示例:https://codepen.io/bvanjoi/pen/BaQXRJb