React18原理: 渲染与更新时的重点关注事项

概述

  • react 在渲染过程中要做很多事情,所以不可能直接通过初始元素直接渲染
  • 还需要一个东西,就是虚拟节点,暂不涉及React Fiber的概念,将vDom树和Fiber 树统称为虚拟节点
  • 有了初始元素后,React 就会根据初始元素和其他可以生成虚拟节点的东西生成虚拟节点
  • React一定是通过虚拟节点来进行渲染的

常用节点类型

  • 除了初始元素能生成虚拟节点以外,还有哪些可能生成虚拟节点?总共有多少节点类型?

1. Dom节点 (ReactDomComponent)

  • 此dom非彼dom, 这里的dom指的是虚拟dom节点,当初始化元素的type属性为字符串的时候
  • React 就会创建虚拟dom节点,例如,前面使用 jsx 直接书写的 const B = <div></div>
  • 它的属性就是div, 可以打印出来 { type: 'div' }

2. 组件节点 (ReactComposite)

  • class组件和函数式组件
  • type 有两类:class App 或 f Test() 这种举例

3. 文本节点 (ReactTextNode)

  • 直接书写字符串或数字,React 会创建为文本节点
  • 比如,我们可以直接用 ReactDOM.render 方法直接渲染字符串或数字
    import ReactDOM from 'react-dom/client';const root = ReactDOM.createRoot(document.getElementById('root'));// root.render('一头猪') // 创建文本节点
    root.render(1111); // 创建文本节点
    

4. 空节点(ReactEmpty)

  • 我们平时写 React 代码的时候,经常会写三目表达式 {this.state.xxx ? <App/> : false}
  • 用来进行条件渲染,只知道为 false 就不会渲染,到底是怎么一回事?
  • 其实,遇到字面量 null, false, true, undefined 在 React 中均会被创建一个空节点
  • 在渲染过程中,如果遇到空节点,那么它将什么都不会做
    import ReactDOM from 'react-dom/client'const root = ReactDOM.createRoot(document.getElementById('root'));
    // root.render(flase); // 创建空节点 // root.render(true);
    // 创建空节点 root.render(null)
    root.render(undefined) // 创建空节点
    

5. 数组节点(ReactArrayNode)

  • 不是渲染数组本身,当React遇到数组时,会创建数组节点,但是不会直接进行渲染
  • 而是将数组里的每一项按出来,根据不同节点类型去做相应的事情
  • 所以,数组里的每一项只能是这里提到的五个节点类型

渲染过程

  • 通过 document.createElement 创建的元素就是真实的dom
  • React 的工作是通过初始元素或可以生成虚拟节点的东西生成虚拟节点然后针对不同的节点类型去做不同的事情最终生成真实dom挂载到页面上
  • 渲染原理
    • 初始元素和可以生成虚拟节点的东西
    • 虚拟节点:根据不同的节点去做不同的事情
    • 挂载到界面(UI)

首次渲染阶段

  • React 会根据初始元素先生成虚拟节点,然后做了一系列操作后最终渲染成真实的UI

  • 根据不同的虚拟节点来看它到底做了些什么处理?

  • 1 )初始元素-dom节点

    • 对于初始元素的 type 属性为字符串时,React会通过 document.createElement 来创建真实DOM
    • 因为,初始元素的 type 为字符串,所以直接会根据 type 属性创建不同的真实DOM
    • 创建完真实DOM后立即设置该真实dom的所有属性,比如,直接在jsx中可以直接书写的 className, style 等都会作用到真实dom上
      // jsx 语法: React初始元素
      const B = <div class='wrapper' style={{color: 'red'}}><p className='text'>123</p>
      </div>
      
    • 当然 html 结构肯定不止一层,所以,在设置完属性后React会根据children属性进行递归遍历
    • 根据不同的 节点类型 去做不同的事情,同样的,如果 children 是初始元素,创建真实dom、设置属性
    • 然后检查是否有子元素,重复次步骤,移植到最后一个元素位置,遇到其他节点类型会做以下事情
  • 2 )初始元素-组件节点

    • 如果初始元素的 type 属性是一个 class 类 或 function 函数时
    • 那么会创建一个组件节点,所以,针对类或函数组件, 它的处理是不同的
    • 函数组件
      • 对于函数组件会直接调用函数,将函数的返回值进行递归处理
      • 看看是什么节点类型,然后去做对应的事情,所以一定要返回能生成虚拟节点的东西
      • 最终生成一棵vDOM树
    • 类组件
      • 对于类组件而言,会相对麻烦一些
        • a. 首先创建类的实例(调用constructor)
        • b. 调用生命周期方法 static getDerivedStateFromProps
        • c. 调用生命周期方法 render, 根据返回值递归处理,跟函数组件处理返回值一样,最终生成一棵 vDom树
        • d. 将该组件的生命周期方法 componentDidMount 加入到执行队列中等待真实dom挂载到页面后执行
        • 注意
          • 前面说了 render 是一个递归处理,所以如果一个组件存在 父子关系的时候
          • 那么肯定要等子组件渲染完
        • 父组件才能走出 render, 所以,子组件的 componentDidMount 一定是比父组件
        • 先入队列的,肯定先运行
  • 3 )文本节点

    • 针对文本节点,会直接通过 document.createTextNode 创建真实的文本节点
  • 4 )空节点

    • 如果生成的是 空节点,那么它将什么都不会做
  • 5 )数组节点

    • 就像前面提到的一样,React不会直接渲染数组,而是将里面的每一项拿出来遍历
    • 根据不同的节点类型去做不同的事,直到递归处理完数组里的每一项 (这里流一个问题,为何数组里要写 key)
  • 注意,嵌套组件渲染时的大致执行顺序

    • 先执行父组件的 constructor, getDerivedStateFromProps, render
    • 再执行子组件的 constructor, getDerivedStateFromProps, render, componentDidMount
    • 最后执行父组件的 componentDidMount

更新与卸载

  • 挂载完成后组件进入活跃状态,等待数据的更新进行重新渲染
  • 那么到底有几种场景会触发更新?整个过程又是怎么样的,有哪些需要注意的地方?

组件更新(setState)

  • 最常见的,我们经常用 setState 来重新设置组件的状态进行重新渲染
  • 使用setState只会更新调用此方法的类。不会涉及到兄弟节点以及父级节点
  • 影响范围仅仅是自己的子节点,步骤如下:
    • 1 ) 运行当前类组件的生命周期静态方法static getDerivedStateFromProps,根据返回值合并当前组件的状态
    • 2 ) 运行当前类组件的生命周期方法shouldComponentUpdate,如果该方法返回的false,直接终止更新流程
    • 3 ) 运行当前类组件的生命周期方法render,得到一个新的vDom树,进入新旧两棵树的对比更新
    • 4 ) 将当前类组件的生命周期方法 getSnapshotBeforeUpdate 加入执行队列,等待将来执行
    • 5 ) 将当前类组件的生命周期方法 componentDidUpdate 加入执行队列,等待将来执行
    • 6 ) 重新生成vDom树
    • 7 ) 执行队列,此队列存放的是更新过程涉及到原本存在的类组件的 生命周期 方法 getSnapshotBeforeUpdate
    • 8 ) 根据vDom树更新真实DOM
    • 9 ) 执行队列,此队列存放的是更新过程涉及到原本存在的类组件的 生命周期 方法 componentDidUpdate
    • 10 ) 执行队列,此队列存放的是更新过程中所有卸载的类组件的 生命周期方法 compoentWillUnmount

根节点更新(ReactDOM.createRoot().render)

  • 在ReactDOM的新版本中,已经不是直接使用 ReactDOM.render 进行更新了
  • 而是通过 createRoot (要控制的DOM区域)的返回值来调用 render
    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import'./index.css';
    import App from'./App';const root = ReactDOM.createRoot(document.getElementById('root');
    root.render(<App/>
    );
    

对比更新过程(diff)

  • 知道了两个更新的场景以及会运行哪些生命周期方法后,我们来看一下具体的过程到底是怎么样的。
  • 所谓对比更新就是将新vDom树跟之前首次渲染过程中保存的老vDom树对比发现差异然后去做一系列操作的过程。
  • 那么问题来了,如果我们在一个类组件中重新渲染了,React怎么知道在产生的新树中它的层级呢?
  • 难道是给vDom树全部挂上一个不同的标识来遍历寻找更新的哪个组件吗?
  • 当然不是,我们都知道React的diff算法将之前的复杂度0(n^3)降为了0(n)
  • 它做了以下几个假设:
    • 1.假设此次更新的节点层级不会发生移动(直接找到旧树中的位置进行对比)
    • 2.兄弟节点之间通过key进行唯一标识
    • 3.如果新旧的节点类型不相同,那么它认为就是一个新的结构
      • 比如之前是初始元素div现在变成了初始元素 span那么它会认为整个结构全部变了,
      • 无论嵌套了多深也会全部丢弃重新创建

key的作用

  • 如果列表里面有初始元素,并且没有给初始元素添加 key那么它会警告

    • Warning: Each child in a list should have a unique “key” prop. 。
  • 那么 key值到底是干嘛用的呢?

    • 其实key的作用非常简单,仅仅是为了通过旧节点
    • 寻找对应的新节点进行对比提高节点的复用率
  • 现在来举个例子,假如现在有五个兄弟节点更新后变成了四个节点

  • 未添加key

  • 添加了key

找到对比目标-节点类型一致

  • 经过假设和一系列的操作找到了需要对比的目标
  • 如果发现节点类型一致,那么它会根据不同的节点类型做不同的事情
  1. 初始元素-DOM节点
  • 如果是DOM节点,React会直接重用之前的真实DOM
  • 将这次变化的属性记录下来,等待将来完成更新
  • 然后遍历其子节点进行递归对比更新
  1. 初始元素-组件节点
  • 函数组件
    • 如果是函数组件,React仅仅是重新调用函数拿到新的vDom树,然后递归进行对比更新
  • 类组件
    • 针对类组件,React也会重用之前的实例对象。后续步骤如下:
    • 1.运行生命周期静态方法static getDerivedStateFromProps。将返回值合并当前状态
    • 2.运行生命周期方法shouldComponentUpdate,如果该方法返回false,终止当前流程
    • 3.运行生命周期方法render,得到新的vDom树,进行新旧两棵树的递归对比更新
    • 4.将生命周期方法getSnapshotBeforeUpdate加入到队列等待执行
    • 5.将生命周期方法componentDidUpdate加入到队列等待执行

3.文本节点

  • 对于文本节点,同样的React也会重用之前的真实文本节点。
  • 将新的文本记录下来,等待将来统一更新(设置nodeValue)

4.空节点

  • 如果节点的类型都是空节点,那么React啥都不会做

5.数组节点

  • 首次挂载提到的,数组节点不会直接渲染
  • 在更新阶段也一样,遍历每一项,进行对比更新,然后去做不同的事

找到对比目标-节点类型不一致

  • 如果找到了对比目标,但是发现节点类型不一致了,这时候类型变了,那么你的子节点肯定也都不一样了
  • 就算一万个子节点,并且他们都是没有变化的,只有最外层的父节点的节点类型变了
  • 照样会全部进行卸载重新创建,与其去一个个递归查看子节点,不如直接全部卸载重新创建
    import'./App.css';
    import React from 'react';function Count(props) {console.log('Count')return <h1>{props. count}</h1>
    }class App extends React. Component {constructor() {super()this.state={arr:[1,2,3]}this.update =this.update.bind(this)}update() {this.setState({arr: [1,2,3,4]})}render() {console.log('父亲render执行')return (<div><button onClick={this.update}>点我更新</button>{ this.state.arr.map((count) => <Count key={count} count={count} />) }</div>)}
    }
    export default App;
    
    • 这个例子,初始化的时候,Count组件被初始化3次
    • 而点击更新的时候,Count组件更新了4次
    • 这是因为它是函数式组件,更新时,仅仅是重新调用函数,拿到新的vDOM树
    • 在react内部加了key,可以复用的是底层的vDom的树,而非这个函数式组件
    • 函数式组件,每次渲染,都会重新执行这个函数,这里要分清两者的区别

未找到对比目标

  • 如果未找到对比的目标,跟 节点类型 不一致的做法类似,
  • 那么对于多出的节点进行挂载流程,对于旧节点进行卸载直接弃用
  • 如果其包含子节点进行递归卸载,对于初始类组件节点会多一个步骤,那就是运行生命周期方法componentWillUnmount。
  • 注意:
    • 尽量保持结构的稳定性,如果未添加key的情况下
    • 兄弟节点更新位置前后错位一个那么后续全部的比较都会错位导致找不到对比目标从而进行卸载新建流程,对性能大打折扣

总结

  • 对于首次挂载阶段
    • 需要了解React的渲染流程
    • 通过书写的初始元素和一些其他可以生成虚拟节点的东西来生成虚拟节点
    • 然后针对不同的节点类型去做不同的事情,最终将真实DOM挂载到页面上
    • 然后执行渲染期间加入到队列的一些生命周期,然后组件进入到活跃状态
  • 对于更新卸载阶段
    • 需要注意的是有几个更新的场景,以及key的作用到底是什么,有或没有会产生多大的影响
    • 还有一些小细节,比如条件渲染时,不要去破坏结构,尽量使用空节点来保持前后结构顺序的统一
    • 重点是新旧两棵树的对比更新流程
    • 找到目标,节点类型一致时针对不同的节点类型会做哪些事,类型不一致时会去卸载整个旧节点
    • 无论有多少子节点,都会全部递归进行卸载

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

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

相关文章

1 月 Web3 游戏行业概览:市场实现空前增长

作者&#xff1a;lesleyfootprint.network 今年一月&#xff0c;区块链游戏领域迎来了爆发式增长&#xff0c;活跃用户的数量大幅提升。 区块链游戏不断融合 AI 技术&#xff0c;旨在提升玩家体验并扩大其服务范围&#xff0c;公链与游戏的兼容性问题也日渐受到重视。技术革新…

three.js 细一万倍教程 从入门到精通(一)

目录 一、three.js开发环境搭建 1.1、使用parcel搭建开发环境 1.2、使用three.js渲染第一个场景和物体 1.3、轨道控制器查看物体 二、three.js辅助设置 2.1、添加坐标轴辅助器 2.2、设置物体移动 2.3、物体的缩放与旋转 缩放 旋转 2.4、应用requestAnimationFrame …

MySQL篇----第二十篇

系列文章目录 文章目录 系列文章目录前言一、NULL 是什么意思二、主键、外键和索引的区别?三、你可以用什么来确保表格里的字段只接受特定范围里的值?四、说说对 SQL 语句优化有哪些方法?(选择几条)前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍…

正则可视化工具:学习和编写正则表达式的利器

引言 正则表达式是一种强大的文本匹配和处理工具&#xff0c;但对于初学者和非专业开发者来说&#xff0c;编写和理解正则表达式可能是一项具有挑战性的任务。为了帮助人们更好地学习和编写正则表达式&#xff0c;正则可视化工具应运而生。本文将探讨正则可视化工具的优点&…

使用python绘制可视化的欧拉公式三维曲线图

欧拉公式应用非常广泛&#xff0c;它在研究交流电、信号分析、量子力学、极坐标切换、求反常积分以及研究任何圆周运动等方面都有着重要的应用&#xff0c;它建立了复数、自然对数、圆周率等重要数学常量之间的关系&#xff0c;描述了复数在极坐标和笛卡尔坐标之间的转换。公式…

4核8g服务器能支持多少人访问?2024新版测评

腾讯云轻量4核8G12M轻量应用服务器支持多少人同时在线&#xff1f;通用型-4核8G-180G-2000G&#xff0c;2000GB月流量&#xff0c;系统盘为180GB SSD盘&#xff0c;12M公网带宽&#xff0c;下载速度峰值为1536KB/s&#xff0c;即1.5M/秒&#xff0c;假设网站内页平均大小为60KB…

用Python来实现2024年春晚刘谦魔术

简介 这是新春的第一篇&#xff0c;今天早上睡到了自然醒&#xff0c;打开手机刷视频就被刘谦的魔术所吸引&#xff0c;忍不住用编程去模拟一下这个过程。 首先&#xff0c;声明的一点&#xff0c;大年初一不学习&#xff0c;所以这其中涉及的数学原理约瑟夫环大家可以找找其…

【CSS】margin塌陷和margin合并及其解决方案

【CSS】margin塌陷和margin合并及其解决方案 一、解决margin塌陷的问题二、避免外边距margin重叠&#xff08;margin合并&#xff09; 一、解决margin塌陷的问题 问题&#xff1a;当父元素包裹着一个子元素且父元素没有边框的时候&#xff0c;当给子元素设置margin-top:100px&…

Go+:一种简单而强大的编程语言

Go是一种简单而强大的编程语言&#xff0c;它是在Go语言之上构建的&#xff0c;旨在提供更加强大、灵活和易于使用的编程体验。Go与Go语言共享大部分语法和语义&#xff0c;因此Go开发人员可以很快上手Go&#xff0c;同时也可以使用Go来编写更加简洁和高效的代码。在本文中&…

【程序设计竞赛】C++与Java的细节优化

必须强调下&#xff0c;以下的任意一种优化&#xff0c;都应该是在本身采用的算法没有任何问题情况下的“锦上添花”&#xff0c;而不是“雪中送炭”。 如果下面的说法存在误导&#xff0c;请专业大佬评论指正 读写优化 C读写优化——解除流绑定 在ACM里&#xff0c;经常出现…

中科大计网学习记录笔记(十):P2P 应用

前言&#xff1a; 学习视频&#xff1a;中科大郑烇、杨坚全套《计算机网络&#xff08;自顶向下方法 第7版&#xff0c;James F.Kurose&#xff0c;Keith W.Ross&#xff09;》课程 该视频是B站非常著名的计网学习视频&#xff0c;但相信很多朋友和我一样在听完前面的部分发现信…

【Java面试】数据类型常见面试题

什么是包装类型 将基本类型包装进了对象中得到的类型 基本类型和包装类型有什么区别 用途不同&#xff1a;基本类型一般用于局部变量&#xff0c;包装类型用于其他地方存储方式不同&#xff1a;用于局部变量的基本类型存在虚拟机栈中的局部变量表中&#xff0c;用于成员变量…

2023年哪个前端框架用的最多?

2023 年&#xff0c;TypeScript 的每月下载量持续稳定增长&#xff0c;年度累计下载量高达2,071,832,110&#xff08;20.7 亿&#xff09;&#xff0c;展现了强大的市场需求和用户认可。 本文来通过详细的数据&#xff08;2023 年 npm 累计下载量&#xff09;&#xff0c;看看…

从计算机恢复已删除文件的 6 种方法!

如果您的重要文件之一已从计算机中删除怎么办&#xff1f;如果不小心从硬盘中删除了文件怎么办&#xff1f; 如今的公司通常将重要数据存储在云或硬盘中。但最重要的是&#xff0c;您必须考虑这样一个事实&#xff1a;您可能会丢失计算机上的数据。数据丢失的原因有多种&#x…

【golang】23、gorilla websocket 源码:examples、数据结构、流程

文章目录 一、examples1.1 echo1.1.1 server.go1.1.2 client.go 1.2 command1.2.1 功能和启动方式1.2.2 home.html1.2.3 main.go 1.3 filewatch1.3.1 html1.3.2 serveHome 渲染模板1.3.3 serveWs1.3.4 writer() 1.4 buffer pool1.4.1 server1.4.2 client 1.5 chat1.5.1 server1…

回归预测模型:MATLAB岭回归和Lasso回归

1. 岭回归和Lasso回归的基本原理 1.1 岭回归&#xff1a; 岭回归&#xff08;Ridge Regression&#xff09; 是一种用于共线性数据分析的技术。共线性指的是自变量之间存在高度相关关系。岭回归通过在损失函数中添加一个L2正则项&#xff08; λ ∑ j 1 n β j 2 \lambda \s…

使用PyOD进行异常值检测

异常值检测各个领域的关键任务之一。PyOD是Python Outlier Detection的缩写&#xff0c;可以简化多变量数据集中识别异常值的过程。在本文中&#xff0c;我们将介绍PyOD包&#xff0c;并通过实际给出详细的代码示例 PyOD简介 PyOD为异常值检测提供了广泛的算法集合&#xff0c…

相机图像质量研究(11)常见问题总结:光学结构对成像的影响--像差

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…

网络协议与攻击模拟_17HTTPS 协议

HTTPShttpssl/tls 1、加密算法 2、PKI&#xff08;公钥基础设施&#xff09; 3、证书 4、部署HTTPS服务器 部署CA证书服务器 5、分析HTTPS流量 分析TLS的交互过程 一、HTTPS协议 在http的通道上增加了安全性&#xff0c;传输过程通过加密和身份认证来确保传输安全性 1、TLS …

网络安全检查表

《网络攻击检查表》 1.应用安全漏洞 2.弱口令&#xff0c;默认口令 3.服务器互联网暴露 4.操作系统&#xff0c;中间件安全漏洞 5.研发服务器&#xff0c;邮件服务器等安全检查