React 中的列表渲染要加 key的原因分析

 更新时间:2022年07月01日 09:50:39   作者:前端西瓜哥  
这篇文章主要介绍了React 中的列表渲染为什么要加 key,在 React 中我们经常需要渲染列表,比如展示好友列表,文中给大家介绍了列表渲染不提供 key 会如何,通过实例代码给大家介绍的非常详细,需要的朋友一起看看吧

在 React 中我们经常需要渲染列表,比如展示好友列表。

常用写法是用 Arrary.prototype.map 方法,将数组形式的数据映射为 JSX.Element 数组,并嵌入到组件要返回的 JSX.Element 中,如下:

function FriendList() {
  const [items, setItems] = useState(['脚本之家', '小明', '张三']);
  return (
    <ul>
      {items.map((item) => (
        <li key={item}>{item}</li>
      ))}
    </ul>
  );
}

你需要给每个项提供 key 属性作为标识,以区分不同的项。如果你不加 key,React 会警告你:

Warning: Each child in a list should have a unique "key" prop.

为什么需要 key?

在回答这个问题之前,我们先简单了解一下 React 的 DOM Diff 算法原理。

React 会在状态发生变化时,对真实 DOM 树按需批量更新,产生新的 UI。

为此底层做的工作是:将新旧两棵虚拟 DOM 树进行 diff 对比,计算出 patch 补丁,打到真实 DOM 树上。

为了高效,React 的 diff 算法做了限制:

  • 只做同层级的节点对比,不跨层级比较。
  • 如果元素的类型不同(如从 p 变成 div),那它们就是不相同的,会销毁整个旧子树,并调用其下组件的卸载钩子,然后再创建全新的树,相当消耗性能。
  • 如果类型相同,会进行打补丁操作(如更新 className 和标签下的文本内容)。

但这样做会有一个问题,如果同级的多节点  只是位置发生了变化 ,但因为相同索引位置对不上,又发现不能复用,就要销毁一棵树并创建一棵新树,实在是太过于低效了。

于是 React 给开发者提供 key 来标记节点,来优化 React diff 算法,告知 React 某个节点其实没有被移除或不能被原地复用,只是换了位置而已,让 React 更新一下位置。

列表渲染不提供 key 会怎样?

不提供 key,React 就无法确定某个节点是否移动了。

React 就只会对比相同位置的两个节点,如果它们类型相同(比如都是 li 元素),就会对比 props 的不同,进行 props 的打补丁。

​因为 列表渲染通常都是相同的类型,所以位置变动时,多半是会触发节点原地复用效果,倒是不用担心树的销毁重建发生。

原地复用在不提供 key 的时候有时候也是能正确渲染的。

除了一种情况,就是 这个节点有自己的内部状态,最经典的莫过于输入框。

function FriendList() {
  const [items, setItems] = useState(['脚本之家', '小明', '张三']);
  const swap = () => {
    [items[0], items[1]] = [items[1], items[0]];
    setItems([...items]);
  };
  return (
    <div>
      <ul>
        {items.map((item) => (
          <li>{item}<input /></li>
        ))}
      </ul>
      <button onClick={() => { swap(); }}>
        交换
      </button>
    </div>
  );
}

我们给第一和第二个输入框输入内容。

再点击 “交换” 按钮,交换数组第一和第二个元素位置。

然后我们看到 input 前面的文字正确交换了,但是输入框里的内容却没有交换。

​原因是 React 做了原地复用,而 input 没有传 props,不需要打 props 补丁,保持了原样。

这个问题怎么解决?加 key。让 React 知道你的节点需要移动,你得这样写:​

items.map((item) => (
  <li key={item}>{item}<input /></li>
))

不使用 key 的另一个缺点是:因为原地复用会使传入的 props 发生变化,导致不能利用好 React.memo 的组件缓存能力。

列表渲染的 key 用数组索引会怎样?

效果和不使用 key 相同,依旧是新旧节点的相同索引位置对比,但是控制台不会打印警告。

应该用什么值作为 key?

对于节点,你需要用一个唯一的 id 赋值给 key,通常会是数组的 id,比如后端返回的好友列表的好友 id。

const [items, setItems] = useState([
  { id: 5, name: '脚本之家' },
  { id: 9, name: '小明' },
  { id: 87, name: '张三' },
  { id: 91, name: '脚本之家' }
]);
const list = items.map((item) => (
  <li key={item.id}>{item.name}</li>
));

如果后端没有返回 id,你可以自己手动用一个 id 生成器补上一个 id,虽然不太优雅就是了。比如:

const items = ['脚本之家', '张三'];
const genId = (() => {
  let i = 0;
  return () => {
    return i++;
  }
})();
const itemsWithId = items.map(item => ({ id: genId(), val: item }));
// [{id: 0, val: '脚本之家'}, {id: 1, val: '张三'}]

对了,这个 key 只需要在同一个层级的节点唯一即可,不要求所有层级的 key 都是唯一的。

另外,如果你确保你的列表渲染后直到被销毁,不会有位置上的变化,可以使用数组索引为 key。

结尾

对于列表的渲染,我们有必要提供 key,来对节点进行区分,React 的 DOM Diff 算法会基于 key 进行节点位置的调整,确保一些涉及到内部状态的节点的渲染状态。

通常来说,key 值应该是唯一的,通常来自后端返回的数据。在你确认列表不会发生位置变更时,可以使用数组索引作为 key,以去掉恼人的警告提示。

有一个点需要说明的是,key 并不是列表渲染的专属,普通的节点也可以用 key。

到此这篇关于React 中的列表渲染为什么要加 key的文章就介绍到这了,更多相关React列表渲染 key内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • React+ResizeObserver实现自适应ECharts图表

    React+ResizeObserver实现自适应ECharts图表

    ResizeObserver是JavaScript的一个API,用于监测元素的大小变化,本文主要为大家介绍了React如何利用ResizeObserver实现自适应ECharts图表,需要的可以参考下
    2024-01-01
  • React TypeScript 应用中便捷使用Redux Toolkit方法详解

    React TypeScript 应用中便捷使用Redux Toolkit方法详解

    这篇文章主要为大家介绍了React TypeScript 应用中便捷使用Redux Toolkit方法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • React里的Fragment标签的具体使用

    React里的Fragment标签的具体使用

    本文主要介绍了React里的Fragment标签的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01
  • React中用@符号编写文件路径实现方法介绍

    React中用@符号编写文件路径实现方法介绍

    在Vue中,我们导入文件时,文件路径中可以使用@符号指代src目录,极大的简化了我们对路径的书写。但是react中,要想实现这种方式书写文件路径,需要写配置文件来实现
    2022-09-09
  • React项目build打包页面空白的解决方案

    React项目build打包页面空白的解决方案

    React项目执行build命令后,在本地服务器打开页面是空白的,本文主要介绍了React项目build打包页面空白的解决方案,感兴趣的可以了解一下
    2023-08-08
  • React初始化渲染过程示例详解

    React初始化渲染过程示例详解

    这篇文章主要为大家介绍了React初始化渲染过程示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • React函数式组件与类组件的不同你知道吗

    React函数式组件与类组件的不同你知道吗

    这篇文章主要为大家详细介绍了React函数式组件与类组件的不同,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03
  • react项目使用redux初始化方式

    react项目使用redux初始化方式

    这篇文章主要介绍了react项目使用redux初始化方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • react导出excel文件的四种方式

    react导出excel文件的四种方式

    本文主要介绍了react导出excel文件的四种方式,主要包括原生js导出,使用 js-export-excel,使用xlsx导出, 使用react-html-table-to-excel,感兴趣的可以了解一下
    2023-11-11
  • 每天学习一个hooks useMount

    每天学习一个hooks useMount

    这篇文章主要为大家介绍了每天学习一个hooks useMount,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05

最新评论