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.tpcf.cn/news/849724.html

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

相关文章

Flutter与iOS原生混合开发 iOS项目集成Flutter

1.创建flutter module 进入iOS工程根目录的上一级&#xff0c;创建flutter module工程 flutter create --template module fluttermodule2.进入iOS工程根目录&#xff0c;编辑podfile文件 flutter_application_path ../fluttermodule load File.join(flutter_application_pa…

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集群…

OpenGL相关库下载并解决三个入门问题

写在前面&#xff1a; 1.本篇文章将解决 freeglut.h 和 glut.h两个库在VS2019下的配置问题&#xff0c;并解决三个OpenGL课本上的三个问题&#xff1a; 利用OpenGL实现折线和矩形的橡皮筋绘制技术&#xff0c;并采用右键菜单实现功能的选择。 利用OpenGL&#xff0c;分别用…

使用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…

2024.6.7力扣刷题记录-链表篇学习记录

目录 一、学习视频 二、视频跟练 1.206. 反转链表 2.92. 反转链表 II 3.25. K 个一组翻转链表 三、课后习题 1.24. 两两交换链表中的节点 &#xff08;1&#xff09;循环两次 &#xff08;2&#xff09;直接反转 &#xff08;3&#xff09;递归 2.445. 两数相加 II &…

一个可以自动生成随机区组试验的excel VBA小程序2

本程序用于应对随机区组试验中要求相同小区位置不能出现同一品种的情况。编程思路略有不同&#xff0c;故将另开一篇。 本试验设计是在原来的基础上改版的&#xff0c;相关的参数设置与操作同上一版&#xff0c;这里不在赘述&#xff1a;一个可以自动生成随机区组试验的excel V…

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

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

微信小程序使用bindtap事件data-xxx传值无法获取

文章目录 1.实例代码2.原因分析3.解决办法 1.实例代码 index.wxml <view data-hi"数据1" bindtap"menuTouch"><image mode"aspectFit" src"{{item.src}}"></image><text class"menu-item-text">{{…

BC9 printf的返回值

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

Java运算符介绍及其用法

运算符 1.算数运算符 符号说明加法-减法*乘法/除法如果符号前后都是整数,结果取整数部分如果符号前后有一个为小数,结果就是正常小数%模,取余数部分 public class Demo01Arithmetic {public static void main(String[] args) {int i 10;int j 3;int add ij;//推荐使用Sys…

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

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

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

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

sql server 把表的所有的null改为0,不要限制某列

DECLARE tableName NVARCHAR(256) Linear -- 替换为你的表名 DECLARE sql NVARCHAR(MAX) SELECT sql UPDATE tableName SET COLUMN_NAME 0 WHERE COLUMN_NAME IS NULL; FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME tableName AND TABLE_SCHEM…

docker_如何推送镜像到仓库(hub.docker.com)

在执行 docker push 时收到 denied: requested access to the resource is denied 错误通常意味着你没有权限将镜像推送到目标存储库。这可能有几个原因,包括: 未登录 Docker Hub:你还没有登录到 Docker Hub,或者你登录的账户没有权限推送到目标存储库。存储库不存在:目标…

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

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

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

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

频谱 搬移

为什么一个信号与一个频率固定的余弦信号相乘&#xff0c;频域上&#xff0c;相当于对信号的频谱进行了一个移动处理? 这个现象可以通过傅里叶变换和调制定理来解释。 数学解释 设信号 x ( t ) x(t) x(t) 和余弦信号 cos ⁡ ( 2 π f c t ) \cos(2\pi f_c t) cos(2πfc​t)…