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

目录

  • 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.mzph.cn/news/849724.shtml

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

相关文章

Nginx 配置防护 缓慢的 HTTP拒绝服务攻击+点击劫持:X-Frame-Options未配置

一 安全团队检测网站 1 检测到目标主机可能存在缓慢的HTTP拒绝服务攻击 缓慢的HTTP拒绝服务攻击是一种专门针对于Web的应用层拒绝服务攻击&#xff0c;攻击者操纵网络,对目标Web服务器进行海量HTTP请求攻击&#xff0c;直到服务器带宽被打满&#xff0c;造成了拒绝服务。 慢…

Flink SQL实践

环境准备 方式1&#xff1a;基于Standalone Flink集群的SQL Client 启动Flink集群 [hadoopnode2 ~]$ start-cluster.sh [hadoopnode2 ~]$ sql-client.sh ... 省略若干日志输出... Flink SQL> 方式2&#xff1a;基于Yarn Session Flink集群的SQL Client 启动hadoop集群…

使用difflib实现文件差异比较用html显示

1.默认方式&#xff0c;其中加入文本过长&#xff0c;需要换行&#xff0c;因此做 contenthtml_output.replace(</style>,table.diff td {word-wrap: break-word;white-space: pre-wrap;max-width: 100%;}</style>)&#xff0c;添加换行操作 ps&#xff1a;当前te…

内存经验分享

目录 内存统计工具 /proc/meminfo Buddy ​​​​​​​​​​​​​​Slub ​​​​​​​Procrank /proc/pid/smaps ​​​​​​​Dumpsys meminfo 内存评估 内存泄漏 Lmk 水位调整 内存统计工具 /proc/meminfo 可以提供整体内存信息&#xff0c;各字段表示的意思如…

mysql工具----dbForgeStudio2020

dbForgeStudio2020&#xff0c;除了基本的操作外&#xff0c;还具有可调试mysql存储过程的功能&#xff0c;是一个不可夺得的mysql软件工具。 本文的软件将简单介绍软件的安装方式&#xff0c;仅供学习交流&#xff0c;不可做它用。 1.安装软件&#xff0c;安装后&#xff0c…

【Linux操作系统】Linux中进程的五种状态:R、S、D、T、X以及僵尸进程、孤儿进程

操作系统中有许多同时执行的进程&#xff0c;这些进程都可能处于不同的状态代表着不同的含义。 R运行状态(running) 概念&#xff1a;并不意味着进程一定在运行中&#xff0c;它表明进程要么是在运行中要么在运行队列里。 我们运行可执行程序myproc利用指令 ps ajx可以看到进程…

BC9 printf的返回值

BC9 printf的返回值 这里我们先要了解库函数printf printf的返回值&#xff0c;是写入的字符总数 我们第一遍写代码时候可能写成这样: #include<stdio.h> int main() {int retprintf("Hello world!");printf("%d", ret);return 0; }我们发现这样是通…

问题:在本案复议阶段,复议机关()。 #其他#媒体

问题&#xff1a;在本案复议阶段&#xff0c;复议机关&#xff08;&#xff09;。 A&#xff0e;有权责令被申请人纠正违法的征税行为 B&#xff0e;应当对被申请人作出的税务具体行政行为所依据的事实证据、法律程序、法律依据及设定权利义务内容的合法性、适当性进行全面审…

【JMeter接口测试工具】第二节.JMeter基本功能介绍(上)【入门篇】

文章目录 前言一、获取所有学院信息接口执行二、线程组的介绍 2.1 并发和顺序执行 2.2 优先和最后执行线程组 2.3 线程组的设置细节三、HTTP请求的介绍四、查看结果树的配置使用总结 前言 一、获取所有学院信息接口执行 我们先针对一条简单的接口进行执行&#…

【Spring Cloud Alibaba】13.自建存储对象服务与集成(minio版)

文章目录 简介什么是云存储服务&#xff08;OSS&#xff09;为什么选择MiniIOMiniIO相关地址 搭建(docker)安装Docker部署MinIO创建存储桶配置存储桶设置存储桶可以直接在浏览器访问 集成到Spring Cloud Alibaba项目创建子模块引入依赖包项目结构配置文件工具类接口类测试 简介…

别让你的品牌失去声音,品牌策划如何成为你的王牌?

品牌策划可不仅仅是一个简单的概念&#xff0c;它是一门真正的艺术和科学。 它涉及到在确立品牌定位之后&#xff0c;进行一系列精心设计的传播和推广活动&#xff0c;从而塑造和管理品牌&#xff0c;让品牌价值达到最大化。 在这个竞争激烈的市场中&#xff0c;想要让你的品…

【人工智能】第三部分:ChatGPT的应用场景和挑战

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

FactoryTalk View Site Edition的VBA基本应用

第一节 在VBA中标签的读取和写入 本例要达到的目标是通过FactoryTalk View Site Edition&#xff08;以下简称SE&#xff09;的VBA来访问PLC中的下位标签&#xff0c;并实现标签的读写。 1.准备工作 打开SE&#xff0c;选择应用程序类型&#xff08;本例是Site Edition Netwo…

燃烧截稿倒计时,NDSS‘25大会即将召开,你的论文准备好了吗?

燃烧截稿倒计时&#xff01;NDSS25大会即将召开&#xff0c;你的论文准备好了吗&#xff1f; 第32届NDSS25(Network and Distributed System Security Symposium)即网络与分布式系统安全研讨会将于2025年2月23日至28日在加利福尼亚州圣地亚哥举行&#xff01; 作为信息安全领域…

【Python机器学习】将PCA用于cancer数据集并可视化

PCA最常见的应用之一就是将高维数据集可视化。一般对于有两个以上特征的数据&#xff0c;很难绘制散点图&#xff0c;。对于Iris&#xff08;鸢尾花&#xff09;数据集&#xff0c;我们可以创建散点矩阵图&#xff0c;通过展示特征所有可能的两两组合来展示数据的局部图像。 不…

MI-SegNet: 基于互信息的超越领域泛化的超声图像分割

文章目录 MI-SegNet: Mutual Information-Based US Segmentation for Unseen Domain Generalization摘要方法实验结果 MI-SegNet: Mutual Information-Based US Segmentation for Unseen Domain Generalization 摘要 针对医学图像分割在不同领域间泛化能力有限的问题,特别是针…

Docker搭建redis-cluster集群

1. 前期准备 1.1 拉redis镜像 docker search redis docker pull redis1. 2 创建网卡 docker network create myredis --subnet 172.28.0.0/16#查看创建的网卡 docker network inspect myredisdocker network rm myredis #删除网卡命令 多个中间 空格隔开 docker network --h…

Python中的Paramiko与FTP文件夹及文件检测技巧

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; Python代码的魅力与实用价值 在当今数字化时代&#xff0c;编程已成为一种不可或缺的技能。Python作为一种简洁、易读且功能强大的编程语言&#xff0c;受到了全球开发者的喜爱。它不仅适用于初学者入门&#xff0c…

配置 jDK 和 Android环境

目录 一、配置jDK 1. 安装 JDK 2. JDK 环境配置 3. JDK的配置验证 二、配置 Android环境 1、下载 2、SDK配置 3、配置Android环境 一、配置jDK 1. 安装 JDK 安装链接&#xff1a;Java Downloads | Oracle 我安装的是 .zip &#xff0c;直接在指定的文件夹下解压就好。…

上位机快速开发框架

右上角向下按钮 -> 后台配置 系统菜单 角色管理 分配权限 用户管理 设备配置 通道管理 首页界面设计 设备1配置 带反馈按钮&#xff0c;如&#xff1a;用户按键00105&#xff0c;PLC反馈状态00106 设备2配置 参数说明&#xff1a; TagName_Main&#xff1a;主要信息&#…