索引作为键是一种反模式

131 阅读3分钟

索引作为键是一种反模式

索引作为键是一种反模式

罗宾·波科尼2015 年 10 月 11 日 3 分钟阅读

很多次我看到开发人员在呈现列表时使用项目的索引作为其键。

{
  todos.map((todo, index) => <Todo {...todo} key={index} />);
}

它看起来很优雅,并且确实消除了警告(这是“真正的”问题,对吗?)。这里有什么危险?

它可能会破坏您的应用程序并显示错误的数据!

让我解释一下,是 React 用来识别 DOM 元素的唯一东西。如果您将项目推入列表或删除中间的项目,会发生什么?如果与之前相同,React 假定 DOM 元素表示与之前相同的组件。但事实并非如此。

Stephen 描述了他在 [egghead.io] 上遇到的问题(https://egghead.io/forums/lesson-discussion/topics/break-up-components-into-smaller-pieces-using-functional-components#post- 6310)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 是多余的,可以避免。例如许可条款或贡献者列表的翻译。

为了帮助您做出决定,我将这些示例的三个共同条件放在一起:

  1. 列表和项目是静态的——它们不是计算出来的,也不会改变;
  2. 列表中的项目没有 ID;
  3. 该列表永远不会重新排序或过滤。

所有这些都满足时,您可以安全地使用索引作为键

更新 2:React、Preact 和 *react

虽然在这篇文章中我写的是 React,但问题并不是它独有的。在类似的库中,比如 Preact,也存在危险。但是,效果可能会有所不同。

请参阅以下 StackOverflow 问题,其中最后一个元素消失了。另请注意 Preact 的创建者 Jason Miller 提供的答案中的解释:Preact 渲染的组件错误