为什么我们需要React列表中的Key?

为什么我们需要 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 也是可以的,只要它们是唯一的且不会被更改的名称。

什么情况下使用索引是可以的呢?

只要你的列表顺序或大小不变,使用索引是可以的。在这种情况下,列表中每个项的位置不会改变,它与索引一样稳定,这个时候我们再使用索引是可以的。

「点点赞赏,手留余香」

1

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系maynote@foxmail.com处理
码云笔记 » 为什么我们需要React列表中的Key?

发表回复