React系列之虚拟DOM、FIBER和DIFF算法

文章目录

  • 虚拟 DOM 和 DIFF 算法
    • 虚拟DOM
      • 虚拟DOM对象
      • 虚拟DOM的优势
      • 预防XSS
    • DIFF算法
      • 旧的DIFF算法
      • Fiber树
      • 渲染过程
      • 算法过程
        • key 的作用

虚拟 DOM 和 DIFF 算法

虚拟DOM

React使用虚拟DOM来更新真正的DOM。
DOM表示“文档对象模型”,浏览器遵循HTML指令来构造文档对象模型。当浏览器加载HTML并呈现用户界面时,HTML文档中所有元素都会变成DOM元素。每次DOM更新的时候都要重新渲染UI,UI越多,DOM更新的成本就越高。
虚拟DOM是一个对象,真实DOM是一个DOM,DOM上默认挂载很多属性和方法,所以从结构上来看:虚拟DOM要比真实DOM轻很多。

在传统的Web应用中,数据的变化会实时地更新到用户界面中,于是每次数据微小的变化都会引起DOM的渲染。而虚拟DOM的目:是将所有的操作聚集到一块,计算出所有的变化后,统一更新一次虚拟DOM。

虚拟DOM对象

    {
        type: 'div',
        props: { class: 'Index' },
        children: [
            {
                type: 'div',
                children: '我是小杜杜'
            },
            {
                type: 'ul',
                children: [
                    {
                        type: 'li',
                        children: 'React'
                    },
                ]
            }
        ]
    }

主要属性有 type:实际的标签;props:标签内部的属性(除key和ref,会形成单独的key名)和 children: 为子节点内容,依次循环。

虚拟DOM的优势

提高效率:
使用原生JS的时候,我们需要的关注点在操作DOM上,而React会通过虚拟DOM来确保DOM的匹配,也就是说,我们关注的点不在时如何操作DOM,怎样更新DOM,React会将这一切处理好。此时,我们更加关注于业务逻辑,从而提高开发效率。

性能提升:
实际上,React会将整个DOM保存为虚拟DOM,如果有更新,都会维护两个虚拟DOM,以此来比较之前的状态和当前的状态,并会确定哪些状态被修改,然后将这些变化更新到实际DOM上,一旦真正的DOM发生改变,也会更新UI。
浏览器在处理DOM的时候会很慢,处理JavaScript会很快。所以在虚拟DOM感受到变化的时候,只会更新局部,而非整体。同时,虚拟DOM会减少了非常多的DOM操作,所以性能会提升很多。

超强的兼容性:
React基于虚拟DOM实现了一套自己的事件机制,并且模拟了事件冒泡和捕获的过程,采取事件代理、批量更新等方法,从而磨平了各个浏览器的事件兼容性问题。

虚拟DOM一定会提高性能吗?
通过上面的理解,很多人认为虚拟DOM一定会提高性能,一定会更快,其实这个说法有点片面,因为虚拟DOM虽然会减少DOM操作,但也无法避免DOM操作。
它的优势是在于diff算法和批量处理策略,将所有的DOM操作搜集起来,一次性去改变真实的DOM, 但在首次渲染上,虚拟DOM会多了一层计算,消耗一些性能,所以有可能会比html渲染的要慢。

预防XSS

React有两层防XSS攻击的设计:
第一是 ReactDOM 负责 DOM 层的所有事务,在渲染所有输入内容前,就会默认进行转义。所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(cross-site-scripting, 跨站脚本)攻击。

第二是在虚拟DOM创建时有一个属性 $$typeof,它是一个特殊的属性,用于标识这是一个 React 元素对象。并且使用 Symbol 数据类型作为 value。用户存储的 JSON 对象可以是任意的字符串,这可能会带来潜在的危险,而 JSON 对象不能存储于 Symbol 类型的变量,React 可以在渲染的时候把没有 typeof 标识的组件过滤掉,从而达到预防 XSS 的功能。
在这里插入图片描述

DIFF算法

旧的DIFF算法

旧的 diff 算法,也就是在 16 版本之前,采用深度遍历的方式,将各个节点及其子节点压入栈中,递归的找完所有节点。
因为整个过程是递归实现的,中间不能中断,中断后需要重新开始,如果树的层级较深,会导致整个更新过程时间(js执行)过长,出现阻碍页面渲染和用户交互卡顿等问题。所以后面才有了 Fiber 链表结构和可以随时中断的 diff。

Fiber树

fiber 是一种数据结构,可以用一个纯 js 对象来表示。

fiber 对象:
{
  type. 节点类型(元素,文本,组件)
  props 节点属性
  stateNode 节点DOM对象 | 组件实例对象
  tag 节点标记 
  effects 数组,存储需要更改的Fiber对象
  effectTag 当前Fiber要被执行的操作 (新增,删除,修改)
  parent 当前Fiber的父级Fiber
  child 当前Fiber的子级Fiber 注意 一个节点只能有一个子级 其他的子级是这个子级的兄弟fiber
  sibling 当前fiber的下一个兄弟Fiber
  alternate Fiber备份 fiber对比时使用
}

在 16 之后,为了优化性能,会先把 ReactElement 树转换成 fiber 树,fiber 树利用了链表的思想,每个节点会维护指向父节点、兄弟节点、子节点的指针。这种链表结构使得 React 能够更灵活地执行任务、暂停和中断渲染过程,将整个更新任务分为多个小任务,在中间可以把执行权交给浏览器执行用户交互和渲染等操作,这样页面卡顿情况减少,提高了用户体验。

从 vdom 转成 fiber 的过程叫做 reconcile(调和),这个过程是可以打断的,由 scheduler 调度执行。
在这里插入图片描述

diff 算法作用在 reconcile 阶段:第一次渲染不需要 diff,直接 ReactElement 转 fiber。再次渲染的时候,会产生新的 ReactElement,这时候要和之前的 fiber 做下对比,决定怎么产生新的 fiber,并对可复用的节点打上修改的标记,剩余的旧节点打上删除标记,新节点打上新增标记。

渲染过程

React 的工作流程主要分为 render (reconcile + schedule) 和 commit 两个阶段:

render 阶段:jsx 在代码编译阶段经 babel 转化成由 React.createElement() 包裹的代码段,在 render 阶段该代码段被执行后便生成了对应的 ReactElement,根据 ReactElement 来创建 Fiber 树;React 采用“双缓存”的思想,因此同一时刻维持有两棵 Fiber 树 ,一颗是应浏览器当前已渲染的 DOM 树,而另一棵则是初始化时或者组件状态更新后由 reconciler 创建的一个工作副本。(可中断)

commit 阶段指的是把新的 Fiber 树渲染到页面上 ,当然这个过程并不会是全量更新,而是根据创建新 Fiber 树时打的一些“标记”(effectTag),来确定在某个 DOM 节点上具体做什么操作,比如更新文本、删除节点等,以尽量小的代价来在 DOM 上还原新 FIber 树 ;新 FIber 树会在 commit 后被标记为 current。(不可中断)

算法过程

diff 算法的目的就是用较少的步骤完成从旧树到新树的转换,为什么不是说最少,因为两棵树的转换的最小时间复杂度算法是On^3,如果节点多的话开销会很大,diff 算法在时间复杂度和操作复杂度之间找了一个平衡,只用了On的时间复杂度完成,它是基于三个前提:

  • 只对同级元素进行Diff。如果一个DOM节点在前后两次更新中跨越了层级,那么React不会尝试复用他。
  • 不同类型的两个元素会产生不同的树
  • 开发人员可以使用 key 属性来表示哪些子元素在不同的渲染中是固定的。

首先比较根节点,

  • 如果节点类型不一致直接删掉重新创建。
  • 如果一致的话分为DOM标签和组件:
    • DOM标签会保留节点,继续对比属性更新属性即可。
    • 组件的话会执行相应的生命周期函数,然后在render中的节点中继续用以上方法对比。

比较子节点时会依照key来尽量保留元素。

实现过程:
分为两个阶段:
第一阶段:一一对比,如果节点类型一样,表示可以服用旧的元素,利用旧fiber和新的元素的props生成新的fiber节点,并打标update。
如果不一样表示不能复用,旧 fiber 打上删除标志,生成新 fiber,并打标新增。
结束第一轮遍历后新旧树都存在没有对比过的元素,说明需要进行二次遍历:

第二阶段:将老 Fiber 节点放入 map,遍历新的 ReactElement 看有没有能复用的,有的话就打上更新的 effectTag。这样遍历完新的 ReactElement 之后,map 里剩下一些,这些是不可复用的,那就删掉,打上删除的 effectTag;如果新的 ReactElement 中还有一些没找到复用节点的,就直接创建,打上新增的 effectTag。这样就完成了更新时的 reconcile 过程。

key 的作用

当同一层级的某个节点添加了对于其他同级节点唯一的key属性,当它在当前层级的位置发生了变化后。react diff算法通过新旧节点比较后,如果发现了key值相同的新旧节点,就会执行移动操作,而不会执行原策略的删除旧节点,创建新节点的操作。这大大提高了React性能和渲染效率。
key只是针对同一层级的节点进行了diff比较优化,而跨层级的节点互相之间的key值没有影响。

不建议用index作为key,比如在用index作key的情况下,当我们对原始的数据list进行了某些元素的顺序改变操作,导致了新旧集合中在进行diff比较时,相同index所对应的新旧的节点其文本不一致了,就会出现一些节点需要更新渲染文本,而如果用了其他稳定的唯一标识符作为key,则只会发生位置顺序变化,无需更新渲染文本,提升了性能。
用index作为key在非受控组件中也会出现bug —> 新增输入框时已输入文本内容移位。

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

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

相关文章

Temu api接口 获取商品详情 数据采集

iDataRiver平台 https://www.idatariver.com/zh-cn/ 提供开箱即用的Temu电商数据采集API,供用户按需调用。 接口使用详情请参考Temu接口文档 接口列表 1. 获取商品详情 参数类型是否必填默认值示例值描述apikeystring是idr_***从控制台里复制apikeycountrystrin…

一文看懂什么是OpenHarmony流转架构

随着全场景多设备的生活方式不断深入,用户拥有的设备越来越多,不同设备都能在适合的场景下提供良好的体验,例如手表可以提供及时的信息查看能力,电视可以带来沉浸的观影体验。但是,每个设备也有使用场景的局限&#xf…

四川思维跳动商务信息咨询有限公司抖音电商的领航者

在数字化浪潮席卷全球的今天,电商行业正以其独特的魅力改变着传统的商业模式。作为这一变革的先锋力量,四川思维跳动商务信息咨询有限公司(以下简称“思维跳动”)凭借其深厚的行业经验和创新思维,专注于抖音电商服务&a…

如何保障MySQL和Redis的数据一致性?

在满足实时性的条件下,不存在两者完全保存一致的方案,只有最终一致性方案。 根据网上的众多解决方案,总结出 6 种,直接看目录: 不好的方案 1. 先写 MySQL,再写 Redis 图解说明: 这是一副时序图…

C++ STL - vector使用详解

目录 0.引言 1.构造函数 2. 赋值函数 3. vector 容量与大小 4. vector 插入和删除 5. vector 元素访问与更改 6. vector 互换 9. vector 预留空间 0.引言 这篇博客将详细介绍 vector,由于总体上与上一篇介绍的 string 类似,在此处注意展示其…

【Python】Data Science with Python 数据科学(1)环境搭建

一、操作系统 使用运行在Windows11主机上的Ubuntu 22.04虚拟机,虚拟化平台为Oracle VM VirtualBox。 二、PyCharm安装 有关PyCharm的安装和快捷方式创建,可分别参考我的博客 Ubuntu安装PyCharm、Ubuntu创建桌面快捷方式 ,以及Ubuntu创建桌…

Vue2(十一):全局事件总线、消息订阅与发布pubsub、TodoList的编辑功能、$nextTick、过渡与动画

一、全局事件总线 1、思路解析 一种组件间通信的方式,适用于任意组件间通信。通俗理解就是一个定义在所有组件之外的公共x,这个x可以有vm或vc上的同款$on、$off、$emit,也可以让所有组件都访问到。 第一个问题:那怎样添加这个x才…

GPU-CPU-ARM-X86-RISC-CUDA

CPU更适合处理复杂逻辑运算和单线程任务,而GPU则更适合处理大规模并行计算任务。 CPU(中央处理器)通常具有较少的核心数量(一般在2到16个之间),但每个核心的性能较强,擅长执行复杂的运算和逻辑…

SpringCloud Alibaba实战和源码(8)OpenFeign使用

1、 使用Feign实现远程HTTP调用 1.1、常见HTTP客户端 HttpClient HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 Http 协 议的客户端编程工具包,并且它支持 HTTP 协议最新版本和建议。HttpClient 相比传统 J…

Postman核心功能解析-参数化和测试报告

一、参数化处理 参数化:针对于某一个接口,有大量的的测试数据需要批量验证,一个一个的更改请求参数太耗时耗力,使用参数化批量处理数据会比较高效,常规通过文档参数化实现。 创建文件 格式CSV 文件内第一行信息 需要…

【YOLOv8代码详解】各个任务Loss损失函数梳理

YOLOv8官方将各类任务(目标检测,关键点检测,实例分割,旋转目标框检测,图像分类)的损失函数封装了在ultralytics\utils\loss.py中,本文主要梳理一下各类任务Loss的大致组成,不涉及到具…

封装-练习

T2、以面向对象的思想,编写自定义类描述IT从业者。设定属性包括:姓名,年龄,技术方向,工作年限;方法包括:工作。 要求: 设置属性的私有访问权限,通过公有的get,set方法实现…

第2章 辐射度、光度和色度学基本理论

一、前言 辐射度学(radiology)是一门以整个电磁波段(electromagnetic band)的电磁辐射能(electromagnetic radiation energy)测量为研究对象的科学。计算机图形学中涉及的辐射度学,则集中于整个…

代码随想录算法训练营三刷day35 |贪心 之 860.柠檬水找零 406.根据身高重建队列 452. 用最少数量的箭引爆气球

三刷day35 860.柠檬水找零406.根据身高重建队列452. 用最少数量的箭引爆气球 860.柠檬水找零 题目链接 解题思路: 局部最优:遇到账单20,优先消耗美元10,完成本次找零。全局最优:完成全部账单的找零。 代码如下&#x…

Internet Explorer 降级

Internet Explorer 降级 1. version2. Internet Explorer 降级References 1. version 帮助 -> 关于Internet Explorer(A) 2. Internet Explorer 降级 开始 -> 控制面板 -> 卸载程序 -> 查看已安装的更新 搜索 Internet -> Internet Explorer 11 -> 卸载 -…

office办公技能|word中的常见使用问题解决方案2.0

一、设置多级列表将表注从0开始,设置为从1开始 问题描述:word中插入题注,出来的是表0-1,不是1-1,怎么办? 写论文时,虽然我设置了“第一章”为一级标题,但是这三个字并不是自动插入的…

[leetcode]118.杨辉三角

前言:剑指offer刷题系列 问题: 给定一个非负整数 *numRows,*生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中,每个数是它左上方和右上方的数的和。 示例: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,…

C# NumericUpDown 控件正整数输入控制

用到了控件的 KeyPress 和 KeyUp事件。 KeyPress 中控制输入“点、空格,负号”; KeyUp 中防止删空,以及防止输入超过最大值或最小值 。 private void nudStart_KeyPress(object sender, KeyPressEventArgs e){numericUpDownKeyPress(sender…

CAPL - 如何实现弹窗提示和弹窗操作(续)

目录 函数介绍 openPanel closePanel 代码示例 1、简单的打开关闭panel面板