索引作为键是一种反模式

罗宾·波科尼2015 年 10 月 11 日 3 分钟阅读
很多次我看到开发人员在呈现列表时使用项目的索引作为其键。
{
todos.map((todo, index) => <Todo {...todo} key={index} />);
}
它看起来很优雅,并且确实消除了警告(这是“真正的”问题,对吗?)。这里有什么危险?
它可能会破坏您的应用程序并显示错误的数据!
让我解释一下,键是 React 用来识别 DOM 元素的唯一东西。如果您将项目推入列表或删除中间的项目,会发生什么?如果键与之前相同,React 假定 DOM 元素表示与之前相同的组件。但事实并非如此。
Stephen 描述了他在egghead.io上遇到的问题
为了演示潜在的危险,我创建了一个简单的示例(带有 source)。
该示例的屏幕截图显示了使用索引作为键的危险。
事实证明,当没有传递任何内容时,React 使用索引作为*键,*因为这是目前最好的猜测。此外,它会警告您它是次优的(它用有点令人困惑的词说,是的)。如果你自己提供它,React 只是认为你知道你在做什么——记住这个例子——可能会导致不可预测的结果。
更好的
每个这样的项目都应该具有永久和独特的属性。理想情况下,它应该在创建项目时分配。当然,我说的是id。然后我们可以通过以下方式使用它:
{
todos.map((todo) => <Todo {...todo} key={todo.id} />);
}
**注意:**首先查看项目的现有属性。他们可能已经有了可以用作id 的东西。
这样做的一种方法是在抽象中将编号向上移动一步。使用全局索引可确保任何两个项目都有不同的id。
todoCounter = 1;
function createNewTodo(text) {
return {
completed: false,
id: todoCounter++,
text,
};
}
好多了
生产解决方案应该使用更强大的方法来处理项目的分布式创建。对于这种情况,我推荐shortid。它快速生成“简短的非顺序 url 友好的唯一”ID。代码可能如下所示:
var shortid = require("shortid");
function createNewTodo(text) {
return {
completed: false,
id: shortid.generate(),
text,
};
}
TL;DR:为每个项目生成一个唯一的id,并在呈现列表时将其用作键。
更新:规则的例外
很多人问他们是否总是,总是必须生成 id。其他人建议使用索引作为键时的用例似乎是合理的。
确实,有时生成新 id 是多余的,可以避免。例如许可条款或贡献者列表的翻译。
为了帮助您做出决定,我将这些示例的三个共同条件放在一起:
- 列表和项目是静态的——它们不是计算出来的,也不会改变;
- 列表中的项目没有 ID;
- 该列表永远不会重新排序或过滤。
当所有这些都满足时,您可以安全地使用索引作为键。
更新 2:React、Preact 和 *react
虽然在这篇文章中我写的是 React,但问题并不是它独有的。在类似的库中,比如 Preact,也存在危险。但是,效果可能会有所不同。
请参阅以下 StackOverflow 问题,其中最后一个元素消失了。另请注意 Preact 的创建者 Jason Miller 提供的答案中的解释:Preact 渲染的组件错误。