React List 的 Key 重复了会发生什么事?

471 阅读2分钟

key 做了什么事

查阅 React 官方文档,文档上是这样描述 React Key。在 React 中,key 是用于帮助 React 识别列表中每个子元素的特殊属性。当你在渲染列表时,你需要为每个子元素提供一个 key 属性。这个 key 属性帮助React 识别哪些元素发生了变化、添加或移除。React 使用 key 来优化列表的更新性能,减少重新渲染的次数,从而提高应用的性能

从上面一段话中提取关键字,key 帮助 React 识别哪些元素发生了变化、添加或移除,我们重点关注这句话

key 导致了什么问题

假设我们有这样的数据

const msgs = [
  {
    id: 1,
    body: "message1",
  },
  {
    id: 2,
    body: "message2",
  },
  {
    id: 2,
    body: "message3",
  },
];

数组的第二项与第三项的 id 是一样的

现在从数组尾部添加新数据,观察其结果

请看下面例子

export const List = () => {
  const [messages, setMessages] = useState(msgs);

  const addNewMessage = () => {
    setMessages((messages) => [
      ...messages,
      { id: messages.length, body: `message${messages.length + 1}` },
    ]);
  };

  return (
    <>
      <div>
        <button onClick={addNewMessage}>add msg</button>
      </div>

      <ul>
        {messages.map(({ id, body }) => (
          <li key={id}>{body}</li>
        ))}
      </ul>
    </>
  );
};

点击 add msg 按钮,观察界面变化,其结果如下

录屏2024-12-02-09.45.29.gif

从数组尾部添加新数据没有任何问题

接着,我们尝试从数组头部添加新数据,会发生什么情况呢?请看下面的测试代码

export const List = () => {
  const [messages, setMessages] = useState(msgs);

  const insertNewMessage = () => {
    setMessages((messages) => [
      { id: messages.length, body: `message${messages.length + 1}` },
      ...messages,
    ]);
  };

  return (
    <>
      <div>
        <button onClick={insertNewMessage}>add msg</button>
      </div>

      <ul>
        {messages.map(({ id, body }) => (
          <li key={id}>{body}</li>
        ))}
      </ul>
    </>
  );
};

录屏2024-12-02-09.59.01.gif

原数组第二项的数据每次添加新数据后否在重复添加

为什么会导致问题

在 React 中,如果列表中的 key 值重复,主要是因为 key 是 React 用来确定哪些元素发生变化、哪些需要被重新渲染或删除的标识符。React 在进行虚拟 DOM diff 算法时,依赖 key 来高效地匹配和更新 DOM 元素,而重复的 key 会被 React Fiber 树保留下来,导致数据错乱

如何设置合理的 key

比如我们要遍历 users 数组,代码如下

const List = () => {
  return (
    <ul>
      {users.map((user, index) => (
        <li key={index}>{user.name}</li>
      ))}
    </ul>
  );
};

如上代码所示,我们不推荐使用 index 来做 key。在动态列表中。虽然数组索引通常是唯一的,但是当列表中的项目被重新排序或者过滤时,索引可能会发生变化,这可能导致React出现错误的更新DOM,甚至导致不必要的重新渲染。

另外,使用索引作为 key 还会导致性能问题。当你添加或者删除一个项目时,React 需要重新计算整个列表中每个项目的 key,这可能会导致性能下降

最好的做法是使用每个项目自身的唯一标识作为 key,比如 users 的 user id。这样可以确保 key 在列表中的唯一性,并且在列表项目重新排序或者过滤时不会出现问题

如何需要遍历的数组中没有唯一标识,那么我们根据具体情况创建一个唯一标识