为什么我们需要React列表中的Key?
我们在 React 开发的时候都会遇到这个警告:Warning: Each child in a list should have a unique “key” prop。首次只要用 React 开发,就会遇到这个警告。
比如,以下列表组件会引发这个警告:
const list = ['Learn React', 'Learn GraphQL']; const ListWithoutKey = () => ( <div> <ul> {list.map((item) => ( <li>{item}</li> ))} </ul> </div> );
出现这个警告就表明,我们只需要为每个列表项元素添加一个 key 属性。由于我们使用的是 JavaScript 内置的数组 map 方法,因此可以访问列表中每个渲染项的索引。现在我们基于上面代码,加上 key 属性:
const ListWithoutKey = () => ( <div> <ul> {list.map((item, index) => ( <li key={index}>{item}</li> ))} </ul> </div> );
上面这样写的话,警告消失了,看起来一切正常。但要注意:使用数组项的索引并不是这里的最佳实践解决方案。
React 要求使用 key 并不是没有原因的。在幕后,React 使用 key 属性将渲染的元素与其在渲染列表中的位置相关联。基本上,它是列表元素中项目元素的标识符。
比如,在使用索引作为 key 的解决方案中,如果重新排序列表,就会出现问题,因为索引会保持不变,但实际的列表项已经改变了位置。如果我们反转数组,那么Learn React
这一项将拥有索引 2,而Learn GraphQL
这一项将拥有索引 1。这就与实际渲染的项不再匹配,这就会引发问题。
但是,如果你在查资料如何解决这个问题时,很难找到一个能说明它的真实场景。
大多数教程只解释如何解决这个问题,但没有说明最坏的情况下会发生什么。
接下来我通过以下示例,来展示这个问题:
const initialList = ['Learn React', 'Learn GraphQL']; const ListWithUnstableIndex = () => { const [list, setList] = React.useState(initialList); const handleClick = event => { setList(list.slice().reverse()); }; return ( <div> <ul> {list.map((item, index) => ( <li key={index}> <label> {item} </label> </li> ))} </ul> <button type="button" onClick={handleClick}> Reverse List </button> </div> ); };
上面代码例子展示了相同的列表,这里是使用 React Hooks 来管理状态。新的按钮元素会将我们的列表反转并存储为状态。
运行一下代码,一切都会正常工作,看不出什么问题。
这是因为这个示例中没有渲染太多内容,所以错误被隐藏了。
那如何让这个错误显示出来了?接下来我在渲染的列表项中添加另一个不受控制的元素,这样就可以看到错误发生了:
const initialList = ['Learn React', 'Learn GraphQL']; const ListWithUnstableIndex = () => { const [list, setList] = React.useState(initialList); const handleClick = event => { setList(list.slice().reverse()); }; return ( <div> <ul> {list.map((item, index) => ( <li key={index}> <label> <input type="checkbox" /> {item} </label> </li> ))} </ul> <button type="button" onClick={handleClick}> Reverse List </button> </div> ); };
我在代码中添加了 checkbox 复选框,用来演示我们上面提到的一个不受控制的元素——管理着自己的内部状态。
如果通过复选框选中前两个项目中的第一个,然后用按钮将它们反转,你会发现,虽然列表项的顺序已经改变,但已选中的复选框仍然被渲染在原来的位置。
// 最初呈现的项目列表 [x] Learn React [ ] Learn GraphQL // 在点击反转按钮后,它会变成这样 [x] Learn GraphQL [ ] Learn React
现在,之前不明显的缺陷在我们的浏览器中就出现了。
问题是,我们正在使用数组中每个项目的索引作为 key 属性。即使我们反转了列表,每次渲染时的索引仍然保持不变:
// 最初呈现的项目列表 [x] Learn React (index = 1) [ ] Learn GraphQL (index = 2) // 在点击反转按钮后,它会变成这样 [x] Learn GraphQL (index = 1) [ ] Learn React (index = 2)
这就是为什么重新排序的元素仍然被分配了相同的 key 属性,而 key 属性在 React 中作为标识符。
React 不会更改复选框元素,因为它认为渲染的项仍然保持相同的顺序。
如何解决这个问题,我们可以通过使用稳定的标识符去渲染列表项来解决这个问题:
const initialList = [ { id: 'a', name: 'Learn React' }, { id: 'b', name: 'Learn GraphQL' }, ]; const ListWithStableIndex = () => { const [list, setList] = React.useState(initialList); const handleClick = event => { setList(list.slice().reverse()); }; return ( <div> <ul> {list.map(item => ( <li key={item.id}> <label> <input type="checkbox" /> {item.name} </label> </li> ))} </ul> <button type="button" onClick={handleClick}> Reverse List </button> </div> ); };
现在,无论列表如何重新排序,key 属性都保持不变,因为 id 与列表中的实际项相关联:
// 最初呈现的项目列表 [x] Learn React (id = a) [ ] Learn GraphQL (id = b) // 在点击反转按钮后,它会变成这样 [ ] Learn GraphQL (id = b) [x] Learn React (id = a)
以上就是关于在 React 中渲染列表而不出现任何错误的全部内容。在例子中,我们使用了一个虚构的标识符,当然,使用每个项的 name 属性作为 key 也是可以的,只要它们是唯一的且不会被更改的名称。
什么情况下使用索引是可以的呢?
只要你的列表顺序或大小不变,使用索引是可以的。在这种情况下,列表中每个项的位置不会改变,它与索引一样稳定,这个时候我们再使用索引是可以的。
码云笔记 » 为什么我们需要React列表中的Key?