返回

凭什么随意设置key会引入React BUG?

前端

对于React初学者来说,相信 key 的使用应该算是较早接触到的一项知识了,它能快速让开发者意识到在React中,组件的更新是基于比对而进行的,差异更新可以提升性能。

这里涉及到React中Virtual DOM的概念,在React中渲染页面时,并不会直接通过DOM操作进行,而是先将数据通过JSX语法或createElement方法抽象成一个Virtual DOM,而 key 的作用就是帮助Virtual DOM在更新时区分出那些没有变化的元素,从而省去这些元素在真实 DOM 上的更新操作。

不过,说到React初学者对于 key 的认识,可能就停留在仅知道它能保证列表的正常渲染上,往往对于 key 的具体值并没有什么要求,只要随便设置一个不重复的值,或者使用数组的下标作为值,应该就可以正常使用了。

const App = () => {
  const list = [
    { id: 1, name: "张三" },
    { id: 2, name: "李四" },
    { id: 3, name: "王五" },
  ];

  return (
    <ul>
      {list.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
};

这时可能就有读者会问了,我的 key 是根据唯一的主键(如id)设置的,为什么还会出现这样的错误呢?

前面我们说到,key 只是在Virtual DOM更新时,用以比对以确定那些没有发生变化的元素,从而可以省去那些不需要更新的元素在真实 DOM 上的更新操作,以提升渲染性能。

因此,只要在Virtual DOM更新时,Virtual DOM中每个元素的key是唯一且和真实DOM上元素的key保持一致的,那么Virtual DOM的更新就可以进行,也就不会出现错误了。

不过,由于在绝大多数情况下,我们在React应用中对Virtual DOM的更新,都是通过setState这种状态更新所触发的,而状态更新所对应的Virtual DOM更新,在绝大多数情况下,都是所有Virtual DOM节点树的更新。

这样一来,只要某个Virtual DOM节点树内的 key 出现了重复,那么在Virtual DOM更新时,就有可能出现多个拥有相同 key 的元素。根据前面所说,如果Virtual DOM中每个元素的 key 是唯一且和真实 DOM 上元素的 key 保持一致的,那么 Virtual DOM 的更新就可以进行。

所以,只要在某一棵Virtual DOM节点树的更新时,其内部出现了 key 重复的元素,那么Virtual DOM就无法更新,直接报错:

Invalid hook call. Hooks can only be called inside of the body of a function component.

此处以一个案例来说明这个问题:

const App = () => {
  const [count, setCount] = useState(0);

  const list = [];
  for (let i = 0; i < count; i++) {
    list.push({ id: i, name: `姓名${i}` });
  }

  return (
    <ul>
      {list.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
};
  • 上述代码中,我们用一个循环生成了一个长度为count的数组,数组中每个元素是一个对象,对象有 idname 两个属性,其中 id 是唯一且连续的,而 name 则是 姓名 加上 id 的字符串。

  • 然后我们用这个数组,通过 map 方法生成了一个 li 标签的数组,每个 li 标签的 key 都等于对应的对象 id 属性,然后将这个数组通过 ul 标签渲染了出来。

  • 此时,这个组件的初始渲染没有任何问题,因为根据上面的分析,Virtual DOM中每个元素的 key 是唯一且和真实 DOM 上元素的 key 保持一致的。

  • 不过,当我们点击按钮,调用 setCount 方法时,会将 count 的值加 1,那么在组件更新时,Virtual DOM 就需要更新。此时,Virtual DOM 中每个元素的 key 不再唯一了,因为在创建数组时,为了保证循环的连续性,新添加的数组元素的 id 恰好等于旧数组的最后一个元素的 id,导致 key 出现了重复。

因此,在Virtual DOM更新时,就会出现重复的 key,导致报错。

最后,想要避免这个问题,我们只需要保证Virtual DOM中每个元素的 key 是唯一且和真实 DOM 上元素的 key 保持一致即可,一般我们使用对象的主键(如上面例子中list数组元素的id属性)作为 key 是不会出错的。

若实在是无法保证唯一性,那么也可以使用像 uuid 这样的第三方库来生成唯一的 key