React@16.x(21)渲染流程-更新

news2024/11/17 3:50:47

目录

  • 1,更新的2种场景
  • 2,节点更新
  • 3,对比 diff 更新
    • 3.1,React 的假设
      • 3.1.2,key
    • 2.1,找到了对比的目标
    • 2.1.1,节点类型一致
      • 1,空节点
      • 2,DOM节点
      • 3,文本节点
      • 4,组件节点
        • 1,函数组件
        • 2,类组件
      • 5,数组节点
    • 2.1.2,节点类型不一致
    • 2.2,没有找到对比的目标
  • 4,举例
    • 例1,组件节点类型不一致
    • 例2,子节点结构发生变化
    • 例3,key 的作用

上篇文章介绍了首次渲染时,React 做的事情。
这篇介绍下在是如何更新节点的。

1,更新的2种场景

  1. 重新调用 ReactDOM.render(),触发根节点更新。
  2. 调用类组件实例的 this.setState(),导致该实例所在的节点更新。

2,节点更新

第1种情况,直接进入根节点的对比 diff 更新

第2种情况,调用this.setState()的更新流程:

  1. 运行生命周期函数 static getDerivedStateFromProps;
  2. 运行生命周期函数 shouldComponentUpdate,如果返回 false则到此结束,终止流程
  3. 运行 render,得到一个新的节点,进入该节点的对比 diff 更新
  4. 将生命周期函数 getSnapshotBeforeUpdate加入执行队列,以待将来执行
  5. 将生命周期函数 componentDidUpdate加入执行队列,以待将来执行。

后续步骤

  1. 更新虚拟DOM树;
  2. 完成真实DOM更新;
  3. 依次调用执行队列中的 componentDidMount
  4. 依次调用执行队列中的 getSnapshotBeforeUpdate
  5. 依次调用执行队列中的 componentDidUpdate

注意,这里的 componentDidMount 指的是子组件的,但子组件也不一定会执行(重新挂载)。
另外,涉及到的生命周期函数的执行顺序时,注意父 render 执行完后遍历进入子组件,当子组件的所有生命周期函数执行后,才会跳出循环继续执行父的其他生命周期函数。

3,对比 diff 更新

整体流程:将运行 render 产生的新节点,对比旧虚拟DOM树中的节点,发现差异并完成更新。

问题:如何确定,对比旧虚拟DOM中的哪个节点?

3.1,React 的假设

React 为了提高对比效率,会做以下假设:

  1. 节点不会出现层级移动,这样可以直接在旧树中找到对应位置的节点进行对比。
  2. 不同的节点类型,会生成不同的结构。节点类型指 React 元素的 type 值。
  3. 多个兄弟节点,通过 key 做唯一标识,这样可以确定要对比的新节点。 如果没有 key,则按照顺序进行对比。

3.1.2,key

如果某个旧节点有 key 值,则它在更新时,会寻找相同层级中相同 key 的节点进行对比。

所以,key 值应该在一定范围内(一般为兄弟节点之间)保持唯一,并保持稳定

保持稳定:不能随意更改,比如通过随机数生成,更新后随机数发生变化找不到旧值。(有意为之需要每次都使用新节点的情况除外)

2.1,找到了对比的目标

2.1.1,节点类型一致

根据不同的节点类型,做不同的事情:

1,空节点

无事发生。

2,DOM节点

  1. 直接重用之前的真实DOM对象,
  2. 属性的变化会记录下来,以待将来统一进行更新(此时不会更新),
  3. 遍历该DOM节点的子节点,递归对比 diff 更新

3,文本节点

  1. 直接重用之前的真实DOM对象,
  2. 将新文本(nodeValue)的变化记录下来,以待将来统一进行更新。

4,组件节点

1,函数组件

重新调用函数得到新一个新节点对象,递归对比 diff 更新

2,类组件
  1. 重用之前的实例;
  2. 运行生命周期函数 static getDerivedStateFromProps
  3. 运行生命周期函数 shouldComponentUpdate,如果返回 false则到此结束,终止流程
  4. 运行 render,得到一个新的节点,进入该节点的递归对比 diff 更新
  5. 将生命周期函数 getSnapshotBeforeUpdate加入执行队列,以待将来执行
  6. 将生命周期函数 componentDidUpdate加入执行队列,以待将来执行。

5,数组节点

遍历数组,递归对比 diff 更新

2.1.2,节点类型不一致

卸载旧节点,使用新节点。

1,类组件节点

直接放弃,并运行生命周期函数 componentWillUnmount,再递归卸载子节点。

2,其他节点

直接放弃,如果该节点有子节点,递归卸载子节点。

2.2,没有找到对比的目标

有2种情况:

  • 新的DOM树中有节点被删除,则卸载多余的旧节点。
  • 新的DOM树中有节点添加,则创建新加入的节点。

4,举例

例1,组件节点类型不一致

更新时如果节点类型不一致,那所有的子节点全部卸载,重新更新
不管子节点的类型是否一致。所以如果是类组件,会重新挂载并运行 componentDidMount

下面的例子中,就是因为节点类型发生变化 div --> p,所以当点击按钮切换时,子组件 Child 会重新挂载(3个生命周期函数都会执行),并且 button 也不是同一个。

import React, { Component } from "react";

export default class App extends Component {
    state = {
        visible: false,
    };

    changeState = () => {
        this.setState({
            visible: !this.state.visible,
        });
    };
    render() {
        if (this.state.visible) {
            return (
                <div>
                    <Child />
                    <button onClick={this.changeState}>toggle</button>
                </div>
            );
        } else {
            return (
                <p>
                    <Child />
                    <button onClick={this.changeState}>toggle</button>
                </p>
            );
        }
    }
}

// 子组件
class Child extends Component {
    state = {};

    static getDerivedStateFromProps() {
        console.log("子 getDerived");
        return null;
    }

    componentDidMount() {
        console.log("子 didMount");
    }

    render() {
        console.log("子 render");
        return <span>子组件</span>;
    }
}

例2,子节点结构发生变化

根节点类型一致,子节点结构发生变化。

下面的例子中,节点对比是按照顺序的,参考上文提到的React的假设1和假设3。

所以,当点击出现 h1 元素的节点对比更新过程中,

  1. 对比组件根节点 div,类型一致重用之前的真实DOM对象,遍历子节点。
  2. 新节点 h1 会和原来这个位置的旧节点 button 对比,不一致则删除旧节点 button。
  3. 新节点 button 发现没有找到对比的目标,则没有其他操作。
  4. 通过新虚拟DOM树,完成真实DOM更新。
export default class App extends Component {
    state = {
        visible: false,
    };

    changeState = () => {
        this.setState({
            visible: !this.state.visible,
        });
    };
    render() {
        if (this.state.visible) {
            return (
                <div>
                    <h1>标题1</h1>
                    <button className="btn" onClick={this.changeState}>
                        toggle
                    </button>
                </div>
            );
        } else {
            return (
                <div>
                    <button className="btn" onClick={this.changeState}>
                        toggle
                    </button>
                </div>
            );
        }
    }
}

所以,一般需要改变DOM 结构时,为了提升效率,要么指定 key来直接告诉 React 要对比的旧节点,要么保证顺序和层级一致。

上面的例子可以更改如下,这也是空节点的作用之一

render() {
   return (
        <div className="parent">
            {this.state.visible && <h1>标题1</h1>}
            <button className="btn" onClick={this.changeState}>
                toggle
            </button>
        </div>
    );
}

// 或
render() {
   return (
        <div className="parent">
            {this.state.visible ? <h1>标题1</h1> : null}
            <button className="btn" onClick={this.changeState}>
                toggle
            </button>
        </div>
    );
}

例3,key 的作用

下面的例子,子组件是类组件,有自己的状态,也会更改状态。
父组件以数组的形式渲染多个子组件,同时会在数组头部插入新的子组件。

import React, { Component } from "react";

class Child extends Component {
    state = {
        num: 1,
    };

    componentDidMount() {
        console.log("子 didMount");
    }

    componentWillUnmount() {
        console.log("子组件卸载");
    }

    changeNum = () => {
        this.setState({
            num: this.state.num + 1,
        });
    };
    render() {
        return (
            <div>
                <span>数字:{this.state.num}</span>
                <button onClick={this.changeNum}>加一</button>
            </div>
        );
    }
}

export default class App extends Component {
    state = {
        arr: [<Child />, <Child />],
    };

    addArr = () => {
        this.setState({
            arr: [<Child />, ...this.state.arr],
        });
    };
    render() {
        return (
            <div className="parent">
                {this.state.arr}
                <button className="btn" onClick={this.addArr}>
                    添加
                </button>
            </div>
        );
    }
}

效果:
在这里插入图片描述

会发现,新的子组件加到最后去了,同时会打印一次 子 didMount,并且 componentWillUnmount 并没有执行。

原因:因为没有设置 key,所以在新旧节点对比时,发现第1个节点类型一致,于是重用了之前的实例。直到对比到最后一个发现没有找到对比目标,才会用新的节点来创建真实DOM。

另外,正因为是类组件节点,所以并不会像我们印象中数组没有指定 key 时,如果往数组的开头插入元素,会导致所有的数组元素重新渲染。

增加 key 调整:

export default class App extends Component {
    state = {
        arr: [<Child key={1} />, <Child key={2} />],
        nextId: 3,
    };

    addArr = () => {
        this.setState({
            arr: [<Child key={this.state.nextId} />, ...this.state.arr],
            nextId: this.state.nextId + 1,
        });
    };
    render() {
        return (
            <div className="parent">
                {this.state.arr}
                <button className="btn" onClick={this.addArr}>
                    添加
                </button>
            </div>
        );
    }
}

以上。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1800059.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

width: 100%和 width: 100vw这两种写法有什么区别

width: 100%; 和 width: 100vw; 是两种不同的 CSS 写法&#xff0c;它们在实际应用中会有不同的效果。以下是这两种写法的主要区别&#xff1a; width: 100%; 定义&#xff1a;将元素的宽度设置为其包含块&#xff08;通常是父元素&#xff09;宽度的 100%。效果&#xff1a;元…

c++调用动态库LNK2019无法解析的外部符号LNK1120无法解析的外部命令

严重性 代码 说明 项目 文件 行 禁止显示状态 错误 LNK1120 6 个无法解析的外部命令 ConsoleApplication1 D:\vs_qt_project\ConsoleApplication1\x64\Debug\ConsoleApplication1.exe 1 严重性 代码 说明 项目 文件 行 …

Open vSwitch 数据包处理流程

一、Open vSwitch 数据包转发模式 Open vSwitch 根据不同的模块使用&#xff0c;主要分为两种数据包的转发模式&#xff1a;Datapath 模式和 DPDK 模式&#xff0c;这两种模式的主要区别在于&#xff1a; Datapath 模式&#xff1a; 使用内核空间的网络栈进行数据包的转发性能相…

M1Pro 使用跳板机

Mac (M1 Pro) 通过Iterm2 使用跳板机 1、由于堡垒机&#xff08;跳板机&#xff09;不能支持mac系统终端工具&#xff0c;只支持xshell等win生态。所以我们需要先安装iterm2 装iterms教程 这里头对rz、sz的配置不详细。我们可以这样配置&#xff1a; where iterm2-send-zmod…

WebGL技术的应用场景

WebGL&#xff08;Web Graphics Library&#xff09;是一种用于在网页上渲染交互式3D和2D图形的技术&#xff0c;而无需使用插件。它基于OpenGL ES 2.0 API&#xff0c;可以被JavaScript调用来绘制图形。WebGL的应用场景相当广泛&#xff0c;涵盖从游戏到教育&#xff0c;再到专…

Windows取证分析 | 如何最大程度提升分析效率

本文由安全研究人员Amr Ashraf发表于Cyber5w的官方博客&#xff0c;研究人员在本文中讨论了如何对可疑设备中的内存映像进行安全调查&#xff0c;并利用了Volatility 3和MemProcFS来最大程度提升Windows取证分析的工作效率。 介绍 内存取证是任何计算机取证分析人员的必备技能…

R语言探索与分析14-美国房价及其影响因素分析

一、选题背景 以多元线性回归统计模型为基础&#xff0c;用R语言对美国部分地区房价数据进行建模预测&#xff0c;进而探究提高多元回 归线性模型精度的方法。先对数据进行探索性预处理&#xff0c;随后设置虚拟变量并建模得出预测结果&#xff0c;再使用方差膨胀因子对 多重共…

磁盘怎么分区?3 款最佳免费磁盘分区软件

您可能已经注意到&#xff0c;大多数计算机至少有 2 个分区&#xff1a;一个安装 Windows 操作系统和程序&#xff08;C:&#xff09;&#xff0c;另一个安装其他文件&#xff08;D:&#xff09;。 默认情况下&#xff0c;计算机只有一个硬盘和一个分区。建议创建 2 个或更多分…

kafka-生产者监听器(SpringBoot整合Kafka)

文章目录 1、生产者监听器1.1、创建生产者监听器1.2、创建生产者拦截器1.3、发送消息测试1.4、使用Java代码创建主题分区副本1.5、application.yml配置----v1版1.6、屏蔽 kafka debug 日志 logback.xml1.7、引入spring-kafka依赖1.8、控制台日志 1、生产者监听器 1.1、创建生产…

计算机组成结构—IO方式

目录 一、程序查询方式 1. 程序查询基本流程 2. 接口电路 3. 接口工作过程 二、程序中断方式 1. 程序中断基本流程 2. 接口电路 3. I/O 中断处理过程 三、DMA 方式 1. DMA 的概念和特点 2. DMA 与 CPU 的访存冲突 3. DMA 接口的功能 4. DMA 接口的组成 5. DMA 的…

阿里 Qwen2 模型开源,教你如何将 Qwen2 扩展到百万级上下文

本次开源的 Qwen2 模型包括 5 个尺寸&#xff0c;分别是 0.5B、1.5B、7B、72B、57B&#xff0c;其中 57B 的属于 MoE 模型&#xff08;激活参数 14B&#xff09;&#xff0c;其余为 Dense 模型&#xff0c;本篇文章会快速介绍下各个尺寸模型的情况&#xff0c;然后重点介绍下如…

数据结构初阶 堆(一)

一. 堆的概念和性质 我们在上一篇博客介绍存储二叉树的两种方式 分别是顺序结构和链式结构 数据结构初阶 初识二叉树-CSDN博客 1. 堆的概念 这里注意&#xff01;&#xff01;&#xff01; 这里说的堆和操作系统里面的堆没有半点关系&#xff01;&#xff01;&#xff01; …

环 境 变 量

如果希望某一个文件在 CMD 窗口的任意路径下都可以打开&#xff0c;则需要将该文件的路径存放在环境变量中。 在 CMD 中运行该文件时&#xff0c;优先查看当前路径下的文件&#xff0c;如果没有找到&#xff0c;则进入环境变量中记录的路径下寻找该文件&#xff0c;如果能找到…

kafka的副本机制

目录 Producer的ACKs参数 配置 acks配置为0 acks配置为1 acks配置为-1或者all 副本的目的就是冗余备份&#xff0c;当某个Broker上的分区数据丢失时&#xff0c;依然可以保障数据可用。因为在其他的Broker上的副本是可用的。 Producer的ACKs参数 对副本关系较大的是&…

Go源码--sync库(2)

简介 这边文章主要讲解 Sync.Cond和Sync.Rwmutex Sync.Cond 简介 sync.Cond 经常用来处理 多个协程等待 一个协程通知 这种场景&#xff0c; 主要 是阻塞在某一协程中 等待被另一个协程唤醒 继续执行 这个协程后续的功能。cond经常被用来协调协程对某一资源的访问 ants协程池…

Android存储空间不足?试试这8个快速解决方案!

在当今的科技时代&#xff0c;Android智能手机已成为我们日常生活的重要组成部分&#xff0c;因为它们保存着我们大量的关键数据。然而&#xff0c;随着我们的使用模式不断扩大&#xff0c;手机内部存储的可用性经常变得有限。手机存储空间不足不仅会损害设备的功能和响应能力&…

数据库系统概论(超详解!!!)第十节 过程化SQL

1.Transact-SQL概述 SQL(Structure Query Language的简称&#xff0c;即结构化查询语言) 是被国际标准化组织(ISO)采纳的标准数据库语言&#xff0c;目前所有关系数据库管理系统都以SQL作为核心&#xff0c;在JAVA、VC、VB、Delphi等程序设计语言中也可使用SQL&#xff0c;它是…

Angular 由一个bug说起之六:字体预加载

浏览器在加载一个页面时&#xff0c;会解析网页中的html和css&#xff0c;并开始加载字体文件。字体文件可以通过css中的font-face规则指定&#xff0c;并使用url()函数指定字体文件的路径。 比如下面这样: css font-face {font-family: MyFont;src: url(path/to/font.woff2…

Docker的安装、启动和配置镜像加速

前言&#xff1a; Docker 分为 CE 和 EE 两大版本。CE 即社区版&#xff08;免费&#xff0c;支持周期 7 个月&#xff09;&#xff0c;EE 即企业版&#xff0c;强调安全&#xff0c;付费使用&#xff0c;支持周期 24 个月。 而企业部署一般都是采用Linux操作系统&#xff0c;而…

电脑找不到mfc140udll怎么修复,总结几种靠谱的修复方法

在日常使用电脑娱乐工作的过程中&#xff0c;我们可能会遇到一些错误提示或程序无法正常运行的情况。其中&#xff0c;一个常见的问题是计算机缺失mfc140u.dll文件。这个问题可能会导致某些应用程序无法启动或运行&#xff0c;给工作和学习带来不便。本文将介绍5种解决计算机缺…