原文链接:Index as a key is an anti-pattern 原文作者:Robin Pokorny

我曾经多次看见一些开发者在渲染列表的时候将数组 item 的 index 作为列表的 key。

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

这种写法看起来很优雅,并且也不会出现警告(这才是你“真正”想解决的问题吧)。回到正题,这么做哪里会有问题呢?

这种方法可能会影响你的程序,让其显示错误的数据。

让我来解释一下原因,key 是 React 中唯一用来标识 DOM 元素的东西。当你 push 了一个 item 进入数组或者是删除了数组中间的 item 时会发生什么呢?

如果这个 key 和发生变化之前一样,React 就会认为该 DOM 元素表示着与之前相同的组件。但是这并不一定是正确的。
image.png
Stephen describes the problem he run into on egghead.io

为了演示这个潜在的问题,我创建了一个简单的例子。

原作者代码地址:https://jsbin.com/wohima/edit?output。我自己做了一些修改。

点击查看【codepen】
事实证明,当你没有传值给 key 的时候,React 默认使用 index 作为 key,因为此时选择 index 是最好的选择。除此之外,React 还会警告你这是一个次优的方法(优秀的方法当然是显式的指定 key的值了)。如果你自己提供了 key 的值,那么 React 就会认为你知道自己在做什么,也就不会给你报警告了。不过你要记住这个例子,使用 index 作为 key 的值的时候会出现问题。

更好的做法

每一个 item 都应该有一个永久且独一无二的属性。理想情况下,这个属性应该在这个 item 被创建的时候就被定义好。没错,我说的就是 id。因此,我们可以将上面的代码修改一下。

  1. {
  2. todos.map((todo) => (
  3. <Todo {...todo} key={todo.id} />
  4. ));
  5. }

注意:首先查看一些 item 中存在的元素。有可能 item 内部已经存在了一些属性可以被当做 id。

我们可以提升变量的作用域,使用全局的 index 可以保证任意的两个项目具有不同的 ID。

  1. let todoCounter = 1;
  2. const createNewTodo = (text) => ({
  3. completed: false,
  4. id: todoCounter++,
  5. text
  6. }

更进一步

在生产环境下的解决方案应该使用一种更加健壮的方法去处理这些 items。比如,我推荐使用 nanoid。它会快速的生成一系列短的、不连续的、对 url 友好的、独一无二的 id。代码如下所示:

  1. import { nanoid } from 'nanoid';
  2. const createNewTodo = (text) => ({
  3. completed: false,
  4. id: nanoid(),
  5. text
  6. }

规则中的例外

很多人问我他们是否一定要生成 id 作为 key。其它一些人提供了一些例子 —— 将 index 作为 key 去渲染列表似乎也没有什么问题。

有时候生成新的 id 是多余的,甚至是可以忽略的。这句话是正确的。举个例子,当我们翻译许可列表,或者是贡献列表的时候,就可以不生成 id。

为了帮助你做决定,我将这三个例子的共同条件写出来:

  1. 列表和 items 是静态的 —— 它们并不会被计算也不会改变;
  2. items 在列表中没有 id;
  3. 该列表不会被重新排序或者是被过滤

当满足以上的三个条件的时候,你就可以安全的使用 index 作为 key 了。