写在前头
我多次看到开发人员在呈现列表时使用项的索引作为键。
{ todos.map((todo, index) => ( <Todo {...todo} key={index} /> )); } 复制代码
它看起来很优雅,而且去掉了警告(这才是“真正的”问题,对吧?)这里的危险是什么?
它可能会破坏你的应用程序和显示错误的数据!
让我解释一下,键是React用来识别DOM元素的唯一工具。如果您将一个项目推入列表或删除中间的内容会发生什么?如果键与之前相同,React假定DOM元素表示与之前相同的组件。但这种情况已不复存在。
为了演示潜在的危险,我创建了一个简单的示例(带有源代码)。
示例的截图,显示了使用索引作为键的危险。
结果是,当不传递任何东西时,React使用索引作为键,因为它是目前最好的猜测。此外,它还会警告你,这是次优的(它说的话有点令人困惑,是的)。如果你自己提供它(键),React会认为你知道你在做什么,记住这个例子,会导致不可预测的结果。
Better
每一个这样的项目都应该有一个永久的和唯一的属性。理想情况下,应该在创建项时赋值。当然,我说的是一个id。然后我们可以用下面的方法来使用它:
{ todos.map((todo) => ( <Todo {...todo} key={todo.id} /> )); } 复制代码
注意:首先查看项目的现有属性。有可能他们已经有了可以用作身份证明的东西。
一种方法就是抽象地把编号往上移一步。使用全局索引(global index)可以确保任何两个项目具有不同的id。
let todoCounter = 1; const createNewTodo = (text) => ({ completed: false, id: todoCounter++, text } 复制代码
Much better
生产解决方案应该使用更健壮的方法来处理项的分布式创建。因此,我推荐nanoid。它快速生成短的非顺序的url友好的唯一id。代码可能如下所示:
import { nanoid } from 'nanoid'; const createNewTodo = (text) => ({ completed: false, id: nanoid(), text } 复制代码
注意:
nanoid文档声明它不应该与React一起使用。他们提到在渲染过程中使用它,这是绝对正确的。在这里,我使用它创建一个稳定的ID。所以不要担心,我们谈论不同的事情,这个建议仍然有效。
TL;DR:为每个项目生成一个唯一的id,并在呈现列表时使用它作为键。
Update: Exception from the rule
很多人问他们是否总是要生成id。其他人提出了一些用例,其中使用索引作为键似乎是合理的。
的确,有时生成新的id是多余的,可以避免。例如,翻译许可条款或贡献者名单。
为了帮助你做出决定,我将这些例子的三个共同点放在一起:
- 列表和项是静态的—它们不计算也不更改;
- 列表中的项目没有id;
- 列表从不重新排序或过滤。
当满足所有这些条件时,您可以安全地将索引用作键。
Update 2: React, Preact, and *react
尽管在这篇文章中我写的是React,但问题并不仅限于它。在类似的库中,比如Preact,危险也存在。然而,效果可能是不同的。
请参阅下面的StackOverflow问题,其中最后一个元素消失了。也请注意Preact的创建者提供的答案中的解释,Jason Miller
Wrong components rendered by Preact
Update 3: nanoid
之前本指南推荐的是shortid.。这个ID生成器被弃用了,所以我用nanoid.代替了它。
Update 4: Clarify nanoid + React usage
正如一些人评论的那样,nanoid不建议直接在React的组件渲染中使用。读完这篇文章,我希望你知道为什么。我添加了一个注释,澄清本文中的建议仍然有效。
参考资料及相关文章
- Dynamic Children and Keyed Fragments in React Docs
- Explanation from Paul O’Shannessy
- The importance of component keys in React.js
- React.js and Dynamic Children — Why the Keys are Important
- React animations for a single component, section The key is using key
- Why you need keys for collections in React by Paul Gray
如果你喜欢这篇文章,请不要忘记在下方留言👏。每次鼓掌通知对我来说都是一种激励。 如果你想了解更多,我开设了一个关于JavaScript的YouTube频道,请考虑订阅。从一开始就陪伴我,帮助我变得更好。