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>
        );
    }
}

以上。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

通俗易懂的解释保护性看跌期权和抛补看涨期权!

今天带你了解通俗易懂的解释保护性看跌期权和抛补看涨期权&#xff01;当涉及期权交易时&#xff0c;保护性看跌期权和抛补看涨期权是两种常见的策略&#xff0c;它们的目的都是为了在特定市场情况下对投资进行保护或增强收益。 保护性看跌期权 保护性看跌期权是一种风险管理策…

第八篇——矢量化:象形文字和拼音文字是如何演化的?

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 通过这篇看似在讲文字的演化过程&#xff0c;实际是在说人生应该如何走&a…

多分类混淆矩阵详解

⭐️ 前言 在机器学习和数据科学中&#xff0c;混淆矩阵&#xff08;Confusion Matrix&#xff09;是一个重要的工具&#xff0c;用于评估分类模型的性能。特别是在多分类问题中&#xff0c;混淆矩阵能够清晰地展示模型在每个类别上的预测结果。以下是对多分类混淆矩阵的详细解…

AI做的2024年高考数学试卷,答案对吗?

2024年高考数学考试已经结束&#xff0c;现在呈上数学真题及AI给出的解答。供各位看官欣赏。 总的来说&#xff0c;人工做题两小时&#xff0c;AI解答两分钟。 但是&#xff0c;AI做的答案是否正确&#xff0c;那就要各位看官来评判了&#xff01; 注&#xff1a;试卷来源于…

【MySQL | 第十二篇】重新认识MySQL数据类型

12.理解MySQL数据类型 12.1整数类型 整数类型有五种&#xff1a;tinyint、smallint、mediumint、int、bigint&#xff08;1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;8字节&#xff09;&#xff0c;存储范围为 -2^(N-1) 到 2^(N-1)-1所有整数类型默认有符号数&…

文本审核纠错

探索高效文本审查利器&#xff1a;Word Checker-CSDN博客 GitHub - shibing624/pycorrector: pycorrector is a toolkit for text error correction. 文本纠错&#xff0c;实现了Kenlm&#xff0c;T5&#xff0c;MacBERT&#xff0c;ChatGLM3&#xff0c;LLaMA等模型应用在纠错…

音视频开发19 FFmpeg 视频解码- 将 h264 转化成 yuv

视频解码过程 视频解码过程如下图所示&#xff1a; ⼀般解出来的是420p FFmpeg流程 这里的流程是和音频的解码过程一样的&#xff0c;不同的只有在存储YUV数据的时候的形式 存储YUV 数据 如果知道YUV 数据的格式 前提&#xff1a;这里我们打开的h264文件&#xff0c;默认是YU…

Android无障碍服务

Hi I’m Shendi Android无障碍服务 最近想制作一个记录点击操作并重复播放的工具&#xff0c;用以解放双手&#xff0c;因现在的Android高版本基本上难以Root&#xff0c;所以选择了使用无障碍来实现&#xff0c;在这里记录下来。 Android无障碍 可参考文档&#xff1a;https:…

os和os.path模块

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 目录也称文件夹&#xff0c;用于分层保存文件。通过目录可以分门别类地存放文件。我们也可以通过目录快速找到想要的文件。在Python中&#xff0c;并…

【go】windows环境设置goos

场景 本地环境&#xff1a;windows 生产环境&#xff1a;linux 现想在本地将go脚本编译为可执行二进制文件&#xff0c;转移至生产中进行运行测试。但go build不生效。 方案&#xff08;修改GOOS&#xff09; cmd打开命令行&#xff0c;执行go env查看本地go环境&#xff0c…

Vue3整合Tailwindcss之padding样式类

04 常用基础样式 padding 样式类 什么是内边距 基础样式 ClassPropertiesp-0padding: 0px;px-0padding-left: 0px; padding-right: 0px;py-0padding-top: 0px; padding-bottom: 0px;ps-0padding-inline-start: 0px;pe-0padding-inline-end: 0px;pt-0padding-top: 0px;pr-0pa…

如何设置vue3项目中默认的背景为白色

方法1&#xff1a;通过CSS全局样式 在全局CSS文件中设置&#xff1a; 如果你的项目中有全局的CSS文件&#xff08;如App.vue或专门的CSS文件&#xff09;&#xff0c;你可以直接设置body或html标签的背景颜色。 在src/assets文件夹中&#xff08;或者任何你存放CSS文件的地方&a…

关于使用南墙waf防护halo网站主页请求404报错的解决方案

文章目录 环境说明问题展示原因探究解决方法 环境说明 在1panel应用商店&#xff0c;部署南墙waf(docker版)halo(2.16.1社区版)注意部署过程中注意uuwaf必须勾选允许外部访问&#xff0c;halo可以不勾选[这里为了证明确实是南墙waf的原因&#xff0c;选择勾选] 问题展示 使…

xiaolingcoding 图解网络笔记——基础篇

文章目录 参考一、网络模型有哪几层DMANAPI 机制二、键入网址到网页显示&#xff0c;期间发生了什么&#xff1f;1. HTTP2. DNS3. 协议栈4. TCP5. IP6. MAC7. 网卡8. 交换机9. 路由器10. 服务器 与 客户端的互相扒皮&#xff08;添加、删除头部信息&#xff09;参考图HTTP 请求…

牛客练习赛126(O(n)求取任意大小区间最值)

牛客练习赛126(O(n)求取任意大小区间最值) 牛客练习赛126 A.雾粉与签到题 题意&#xff1a;给出长度为n的数组, 顺序选出任意三个元素&#xff0c;最小化第二个元素 思路&#xff1a; 遍历除了第一个和最后一个元素取最小值即可 AC code&#xff1a; void solve() {int…

迈入智能新纪元:智慧机房运维系统引领行业变革

在数字化飞速发展的今天&#xff0c;机房作为信息时代的“心脏”&#xff0c;其稳定运行对于企业的业务连续性至关重要。然而&#xff0c;传统的机房运维模式面临着诸多挑战&#xff0c;如响应速度慢、故障定位难、资源浪费大等问题。智慧机房运维系统&#xff0c;它将以智能化…

go语言内置预编译 //go:embed xxx 使用详解

在go语言里面&#xff0c;我们可以使用一个“类注释”的语法来来让编译器帮助我们在编译的时候将一些文件或者目录读取到指定的变量中来供我们使用。 go:embed语法&#xff1a; //go:embed 文件或者目录路径 var 变量名 变量类型 说明&#xff1a; 文件或者目录路径 可以…

106.网络游戏逆向分析与漏洞攻防-装备系统数据分析-在UI中显示装备与技能信息

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果&#xff0c;代码看不懂是正常的&#xff0c;只要会抄就行&#xff0c;抄着抄着就能懂了 内容…

鸿蒙轻内核M核源码分析系列十九 Musl LibC

LiteOS-M内核LibC实现有2种&#xff0c;可以根据需求进行二选一&#xff0c;分别是musl libC和newlibc。本文先学习下Musl LibC的实现代码。文中所涉及的源码&#xff0c;均可以在开源站点 https://gitee.com/openharmony/kernel_liteos_m 获取。LiteOS-M内核提供了和内核相关的…

【已解决】关于gedit的Unable to init server: 无法连接: 拒绝连接

&#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主。 &#x1f913; 同时欢迎大家关注其他专栏&#xff0c;我将分享Web前后端开发、人工智能、机器学习、深…