• React循环DOM时为什么需要添加key


    一、React 渲染流程和更新流程

    • react渲染流程:jsx -> 虚拟dom -> 真实dom
    • react更新流程:props/state改变 -> render函数重新执行 -> 生成新的虚拟dom树 -> 新旧虚拟dom树进行diff -> 计算出差异进行更新 ->更新到真实的dom树

    所以在每次更新的时候,React需要基于这两颗不同的树之间的差别来判断如何有效的更新UI,如果一棵树参考另外一棵树进行完全比较更新,那么即使是最先进的算法,该算法的复杂程度为 O(n3),其中 n 是树中元素的数量,如果在React中使用了该算法,那么展示1000个元素所需要执行的计算量将在十亿的量级范围,这个开销太过昂贵了,React的更新性能会变得非常低效;于是React对这个算法进行了优化,将其优化成了O(n),这也就是传说中的diff算法

    二、diff 算法

    diff 算法做了三处优化

    1. 同层节点之间相互比较,不会垮节点比较
    2. 不同类型的节点,产生不同的树结构
    3. 开发中,可以通过key来指定哪些节点在不同的渲染下保持稳定

    2-1 对比不同类型的元素

    <div>
        <Comment />
    div>
    
    <span>
        <Comment />
    span>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2-2 对比同一类型的元素

    • 当比对两个相同类型的 React 元素时,React 会保留 DOM 节点,仅比对及更新有改变的属性

    • 比如下面的代码更改:通过比对这两个元素,React 知道只需要修改 DOM 元素上的 className 属性

    <div className="before" title="stu" />
    <div className="after" title="stu" />
    
    • 1
    • 2
    • 比如下面的代码更改:当更新 style 属性时,React 仅更新有所更变的属性。通过比对这两个元素,React 知道只需要修改 DOM 元素上的 color 样式,无需修改 fontWeight。
    {color="red",fontWeight:"bold"}} />
    {color="green",fontWeight:"bold"}} />
    • 1
    • 2
    • 如果是同类型的组件元素:组件会保持不变,React会更新该组件的props,并且调用componentWillReceiveProps() 和 componentWillUpdate() 方法,下一步调用 render() 方法,diff 算法将在之前的结果以及新的结果中进行递归;

    2-3 对子节点递归

    • 在默认条件下,当递归 DOM 节点的子元素时,React 会同时遍历两个子元素的列表;当产生差异时,生成一个mutation(改变)。

    • 如果在最后插入一条数据的情况:前面两个比较是完全相同的,所以不会产生mutation,最后一个比较,产生一个mutation,将其插入到新的DOM树中即可,但是如果是在前面插入一条数据,React会对每一个子元素产生一个mutation,而不是保持

    • 星际穿越
    • 盗梦空间
    • 的不变;这种低效的比较方式会带来一定的性能问题,所以就得使用key来优化

    后面插一条数据
    <ul>
         <li>星际穿越li>
         <li>盗梦空间li>
     ul>
     <ul>
         <li>星际穿越li>
         <li>盗梦空间li>
         <li>大话西游li>
     ul>
    
     前面插一条数据
     <ul>
         <li>星际穿越li>
         <li>盗梦空间li>
     ul>
     <ul>
         <li>大话西游li>
         <li>星际穿越li>
         <li>盗梦空间li>
     ul>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    参考:前端react面试题详细解答

    三、key

    要切记,在 diff 算法中,可以通过 key 来指定哪些节点在不同的渲染下保持稳定,并且要保证 key 是唯一的,不要使用随机数(随机数在下一次render时,会重新生成一个数字),也不能使用index,这都对性能是没有优化的

    import React, { Component } from "react";
    
    export default class App extends Component {
      constructor(props) {
        super(props);
    
        this.state = {
          movies: ["星际穿越", "盗梦空间"],
        };
      }
    
      render() {
        return (
          <div>
            <h2>电影列表</h2>
            <ul>
              {this.state.movies.map((item, index) => {            return <li key={item}>{item}</li>;          })}        </ul>
            <button onClick={(e) => this.insertMovie()}>添加电影</button>
          </div>
        );
      }
    
      insertMovie() {
        this.setState({
          movies: ["大话西游", ...this.state.movies],
        });
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    代码解析:在默认条件下,当递归 DOM 节点的子元素时,React 会同时遍历两个子元素的列表;当产生差异时,生成一个mutation。如果在movies后面添加数据,前面两个比较是完全相同的,所以不会产生mutation;最后一个比较,产生一个mutation,将其插入到新的DOM树中即可;
    如果在movies前面添加数据,React会对每一个子元素产生一个mutation,而不是保持

  • 星际穿越
  • 盗梦空间
  • 的不变,这种低效的比较方式会带来一定的性能问题,
    当子元素(这里的li)拥有 key 时,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素:
    在下面这种场景下,key为"星际穿越"和"盗梦空间"的元素仅仅进行位移,不需要进行任何的修改; 将key为"大话西游"的元素插入到最前面的位置即可;

  • 相关阅读:
    JS中BigInt的使用
    感悟“驱动力“
    nginx 代理接口报404 问题排查
    瑞芯微 | I2S-音频基础分享
    log4j 基础使用入门教程
    防火墙设置
    学习STM32第十九天
    Django笔记十五之in查询及date日期相关过滤操作
    信息系统项目管理师---第十一章项目风险管理历年考题
    MyBatis-plus 分页功能实现
  • 原文地址:https://blog.csdn.net/beifeng11996/article/details/127644756