沐游虞笔记
  • 前端面试题

    • HTML面试题汇总(无答案)
    • HTML面试题汇总
    • CSS 面试题汇总(无答案)
    • CSS 面试题汇总
    • javascript 面试题汇总(无答案)
    • javascript 面试题汇总
    • promise 面试题(无答案)
    • promise 面试题
    • 浏览器面试题汇总(无答案)
    • 浏览器面试题汇总
    • 网络面试题汇总(无答案)
    • 网络面试题汇总
    • 工程化面试题汇总(无答案)
    • 工程化面试题汇总
    • VUE面试题汇总(无答案)
    • VUE面试题汇总
  • 直播课文件

    • 静态页面学习指导
    • 属性的计算过程
    • 层叠继承规则总结
    • BFC
    • JS基础知识回顾
    • DOM 事件的传播机制
    • DOM 事件的注册和移除
    • 阻止事件默认行为
    • 基础领航考试题
    • 基础领航考试题(答案)
    • 2024前端发展
    • JS核心概念学习指导
    • 第三方库与工程化学习指导
    • Vue入门学习指导
    • vue进阶学习指导
    • 前端性能优化
  • 笔面试环节知识讲解

    • 目录
    • 图像处理
    • 图像处理(面试)
    • Webpack构建优化
    • Webpack构建优化(面试)
    • TTS性能优化
    • TTS性能优化(面试)
    • 实时协作
    • 实时协作(面试)
    • 网页复制图片到剪贴板
    • 网页复制图片到剪贴板(面试)
    • vite插件
    • vite插件(面试)
    • 表单数据同步与保持
    • 表单数据同步与保持(面试)
    • 优化虚拟列表
    • 优化虚拟列表(面试)
    • 微前端解决巨石应用
    • 微前端解决巨石应用(面试)
    • DNS解析与优化
    • DNS解析与优化(面试)
    • 前端监控
    • 前端监控(面试)
    • 12.跨标签页通信
    • 12.跨标签页通信(面试)
    • 13.Vite相关优化
    • 13.Vite相关优化(面试)
    • 14.计时器节流问题
    • 14.计时器节流问题(面试)
    • 15.多文件预览支持
    • 15.多文件预览支持(面试)
    • 16.defer优化白屏时间
    • 16.defer优化白屏时间(面试)
  • Vue3整体变化
  • Vue2响应式回顾
  • Vue3响应式变化
  • nextTick实现原理
  • 两道代码题
  • Vue运行机制
  • 渲染器核心功能
  • 事件绑定与更新
  • computed面试题
  • watch面试题
  • 图解双端diff
  • 图解快速dff
  • 最长递增子序列
  • 模板编译器
  • 模板编译提升
  • 组件name作用
  • 路由传参方式
  • 基础篇

    • 序章React介绍
    • JSX基础语法
    • React基本介绍
    • 表单
    • 生命周期
    • 组件与事件绑定
    • 组件状态与数据传递
    • Hooks
    • React--redux介绍
    • React-router介绍
  • 就业篇

    • 属性默认值和类型验证
    • 高阶组件
    • Ref
    • Context
    • Render Props
    • Portals
    • 错误边界
    • 组件渲染性能优化
    • 前端框架的理解
    • Reacti和Vue描述页面的区别
    • 前端框架的分类
    • 虚拟DoM
    • React整体架构
    • React渲染流程
    • Fiber双缓冲
    • MessageChannel
    • Scheduleri调度普通任务
    • Scheduleri调度延时任务
    • 最小堆
    • React中的位运算
    • beginWork工作流程
    • completeWork工作流程
    • 图解diff算法
    • commit工作流程
    • lane模型
    • React中的事件
    • Hooks原理
    • useStateuseReducer.
    • effect相关hook
    • useCallbackuseMemo
    • useRef
    • Update
    • 性能优化策略之eagerState
    • 性能优化策略之bailout
    • bailoutContextAPl
    • 性能优化对日常开发启示
  • 前端监控概述
  • 错误监控
  • 数据上报
  • 页面性能监控
  • 用户行为收集与埋点
  • CSS3手册
  • HTML5手册
  • JavaScript语言提升

    • es补充
    • 事件循环
    • promise基础
    • Promise的链式调用
    • Promise的静态方法
    • async和await
    • Promise相关面试题
  • 网络

    • 客户端与服务器
    • 关于 Apifox 的使用
  • git文档
  • 工程化

    • CommonJS
    • ES module
    • npm文档(包管理)
    • Lass笔记
    • webpack工具
  • canvas详解
  • uinapp笔记
  • 自动化测试
  • oauth2令牌

    • 认识Oauth2
    • 三方应用实现github授权
    • 微信三方应用登录实现
    • 支付宝沙箱支付功能
  • 前端面试题

    • HTML面试题汇总(无答案)
    • HTML面试题汇总
    • CSS 面试题汇总(无答案)
    • CSS 面试题汇总
    • javascript 面试题汇总(无答案)
    • javascript 面试题汇总
    • promise 面试题(无答案)
    • promise 面试题
    • 浏览器面试题汇总(无答案)
    • 浏览器面试题汇总
    • 网络面试题汇总(无答案)
    • 网络面试题汇总
    • 工程化面试题汇总(无答案)
    • 工程化面试题汇总
    • VUE面试题汇总(无答案)
    • VUE面试题汇总
  • 直播课文件

    • 静态页面学习指导
    • 属性的计算过程
    • 层叠继承规则总结
    • BFC
    • JS基础知识回顾
    • DOM 事件的传播机制
    • DOM 事件的注册和移除
    • 阻止事件默认行为
    • 基础领航考试题
    • 基础领航考试题(答案)
    • 2024前端发展
    • JS核心概念学习指导
    • 第三方库与工程化学习指导
    • Vue入门学习指导
    • vue进阶学习指导
    • 前端性能优化
  • 笔面试环节知识讲解

    • 目录
    • 图像处理
    • 图像处理(面试)
    • Webpack构建优化
    • Webpack构建优化(面试)
    • TTS性能优化
    • TTS性能优化(面试)
    • 实时协作
    • 实时协作(面试)
    • 网页复制图片到剪贴板
    • 网页复制图片到剪贴板(面试)
    • vite插件
    • vite插件(面试)
    • 表单数据同步与保持
    • 表单数据同步与保持(面试)
    • 优化虚拟列表
    • 优化虚拟列表(面试)
    • 微前端解决巨石应用
    • 微前端解决巨石应用(面试)
    • DNS解析与优化
    • DNS解析与优化(面试)
    • 前端监控
    • 前端监控(面试)
    • 12.跨标签页通信
    • 12.跨标签页通信(面试)
    • 13.Vite相关优化
    • 13.Vite相关优化(面试)
    • 14.计时器节流问题
    • 14.计时器节流问题(面试)
    • 15.多文件预览支持
    • 15.多文件预览支持(面试)
    • 16.defer优化白屏时间
    • 16.defer优化白屏时间(面试)
  • Vue3整体变化
  • Vue2响应式回顾
  • Vue3响应式变化
  • nextTick实现原理
  • 两道代码题
  • Vue运行机制
  • 渲染器核心功能
  • 事件绑定与更新
  • computed面试题
  • watch面试题
  • 图解双端diff
  • 图解快速dff
  • 最长递增子序列
  • 模板编译器
  • 模板编译提升
  • 组件name作用
  • 路由传参方式
  • 基础篇

    • 序章React介绍
    • JSX基础语法
    • React基本介绍
    • 表单
    • 生命周期
    • 组件与事件绑定
    • 组件状态与数据传递
    • Hooks
    • React--redux介绍
    • React-router介绍
  • 就业篇

    • 属性默认值和类型验证
    • 高阶组件
    • Ref
    • Context
    • Render Props
    • Portals
    • 错误边界
    • 组件渲染性能优化
    • 前端框架的理解
    • Reacti和Vue描述页面的区别
    • 前端框架的分类
    • 虚拟DoM
    • React整体架构
    • React渲染流程
    • Fiber双缓冲
    • MessageChannel
    • Scheduleri调度普通任务
    • Scheduleri调度延时任务
    • 最小堆
    • React中的位运算
    • beginWork工作流程
    • completeWork工作流程
    • 图解diff算法
    • commit工作流程
    • lane模型
    • React中的事件
    • Hooks原理
    • useStateuseReducer.
    • effect相关hook
    • useCallbackuseMemo
    • useRef
    • Update
    • 性能优化策略之eagerState
    • 性能优化策略之bailout
    • bailoutContextAPl
    • 性能优化对日常开发启示
  • 前端监控概述
  • 错误监控
  • 数据上报
  • 页面性能监控
  • 用户行为收集与埋点
  • CSS3手册
  • HTML5手册
  • JavaScript语言提升

    • es补充
    • 事件循环
    • promise基础
    • Promise的链式调用
    • Promise的静态方法
    • async和await
    • Promise相关面试题
  • 网络

    • 客户端与服务器
    • 关于 Apifox 的使用
  • git文档
  • 工程化

    • CommonJS
    • ES module
    • npm文档(包管理)
    • Lass笔记
    • webpack工具
  • canvas详解
  • uinapp笔记
  • 自动化测试
  • oauth2令牌

    • 认识Oauth2
    • 三方应用实现github授权
    • 微信三方应用登录实现
    • 支付宝沙箱支付功能
  • 基础篇

    • 序章:React课程介绍
    • JSX 基础语法
    • React 基本介绍
    • 表单
    • 生命周期
    • 组件与事件绑定
    • 组件状态与数据传递
    • Hooks
    • React-redux 介绍
    • React-router介绍
  • 就业篇

    • 属性默认值和类型验证
    • 高阶组件
    • Ref
    • Context
    • Render Props
    • Portals
    • 错误边界
    • 组件渲染性能优化
    • 前端框架的理解
    • React和Vue描述页面的区别
    • 前端框架的分类
    • 虚拟 DOM
    • React 整体架构
      • 旧架构的问题
        • CPU 瓶颈
        • I/O瓶颈
      • 新架构的解决思路
        • 解决 CPU 瓶颈
        • 解决 I/O 瓶颈
      • 真题解答
    • React 渲染流程
    • Fiber双缓冲
    • MessageChannel
    • Scheduler调度普通任务
    • Scheduler调度延时任务
    • 最小堆
    • React中的位运算
    • beginWork工作流程
    • completeWork工作流程
    • 图解diff算法
    • commit 工作流程
    • lane模型
    • React 中的事件
    • Hooks原理
    • useState和useReducer
    • effect相关hook
    • useCallback和useMemo
    • useRef
    • Update
    • 性能优化策略之eagerState
    • 性能优化策略之bailout
    • bailout和ContextAPI
    • 性能优化对日常开发启示
  • react
  • 就业篇
luzhichang
2024-10-17
目录

React 整体架构

# React 整体架构

面试题:是否了解过 React 的架构?新的 Fiber 架构相较于之前的 Stack 架构有什么优势?

标准且浅显的回答

Stack 架构在进行虚拟 DOM 树比较的时候,采用的是递归,计算会消耗大量的时间,新的 Fiber 架构采用的是链表,可以实现时间切片,防止 JS 的计算占用过多的时间从而导致浏览器出现丢帧的现象。

React v15以及之前的架构称之为 Stack 架构,从 v16 开始,React 重构了整体的架构,新的架构被称之为 Fiber 架构,新的架构相比旧架构有一个最大的特点就是能够实现时间切片。

  • 旧架构的问题?
  • 新架构的解决思路

# 旧架构的问题

React 是用 JavaScript 构建快速响应的大型 Web 应用程序的首选方式

有哪些情况会导致我们的 Web 应用无法快速响应?

总结起来,实际上有两大类场景会限制快速响应:

  • 当你需要执行大量计算或者设备本身的性能不足的时候,页面就会出现掉帧、卡顿的现象,这个本质上是来自于 CPU 的瓶颈
  • 进行 I/O 的时候,需要等待数据返回后再进行后续操作,等待的过程中无法快速响应,这种情况实际上是来自于 I/O 的瓶颈

# CPU 瓶颈

平时我们在浏览网页的时候,这张网页实际上是由浏览器绘制出来的,就像一个画家画画一样

draw

平时我们所浏览的网页,里面往往会有一些动起来的东西,比如轮播图、百叶窗之类的,本质其实就是浏览器不停的在进行绘制。

目前,大多数设备的刷新频率为 60 FPS,意味着 1秒钟需要绘制 60 次,1000ms / 60 = 16.66ms,也就是说浏览器每隔 16.66ms 就需要绘制一帧。

浏览器在绘制一帧画面的时候,实际上还有很多的事情要做:

image-20221227140043781

上图中的任务被称之为“渲染流水线”,每次执行流水线的时候,大致是需要如上的一些步骤,但是并不是说每一次所有的任务都需要全部执行:

  • 当通过 JS 或者 CSS 修改 DOM 元素的几何属性(比如长度、宽度)时,会触发完整的渲染流水线,这种情况称之为重排(回流)
  • 当修改的属性不涉及几何属性(比如字体、颜色)时,会省略掉流水线中的 Layout、Layer 过程,这种情况称之为重绘
  • 当修改“不涉及重排、重绘的属性(比如 transform 属性)”时,会省略流水线中 Layout、Layer、Print 过程,仅执行合成线程的绘制工作,这种情况称之为合成

按照性能高低进行排序的话:合成 > 重绘 > 重排

前面说过,浏览器绘制的频率是 16.66ms 一帧,但是执行 JS 与渲染流水线实际上是在同一个线程上面执行,也就意味着如果 JS 执行的时间过长,不能够及时的渲染下一帧,也就意味着页面掉帧,表现出来的现象就是页面卡顿。

在 Reactv16 之前就存在这个问题,JS 代码执行的时间过长。在 React 中,需要去计算整颗虚拟 DOM 树,虽然说是 JS 层面的计算,相比直接操作 DOM,节省了很多时间,但是每次重新去计算整颗虚拟 DOM 树,会造成每一帧的 JS 代码的执行时间过长,从而导致动画、还有一些实时更新得不到及时的响应,造成卡顿的视觉效果。

假设有如下的 DOM 层次结构:

image-20230223152638127

那么转换成虚拟 DOM 对象结构大致如下:

{
  type : "div",
  props : {
    id : "test",
    children : [
      {
        type : "h1",
        props : {
          children : "This is a title"
        }
      }
      {
        type : "p",
        props : {
          children : "This is a paragraph"
        }
      },{
        type : "ul",
        props : {
          children : [{
            type : "li",
            props : {
              children : "apple"
            }
          },{
            type : "li",
            props : {
              children : "banana"
            }
          },{
            type : "li",
            props : {
              children : "pear"
            }
          }]
        }
      }
    ]
  }
}

在 React v16 版本之前,进行两颗虚拟 DOM 树的对比的时候,需要涉及到遍历上面的结构,这个时候只能使用递归,而且这种递归是不能够打断的,一条路走到黑,从而造成了 JS 执行时间过长。

image-20221227150133112

这样的架构模式,官方就称之为 Stack 架构模式,因为采用的是递归,会不停的开启新的函数栈。

# I/O瓶颈

对于前端开发来讲,最主要的 I/O 瓶颈就是网络延迟。

网络延迟是一种客观存在的现象,那么如何减少这种现象对用户的影响呢?React 团队给出的答案是:将人机交互的研究成果整合到 UI 中。

用户对卡顿的感知是不一样的,输入框哪怕只有轻微的延迟,用户也会认为很卡,假设是加载一个列表,哪怕 loading 好几秒,用户也不会觉得卡顿。

对于 React 来讲,所有的操作都是来自于自变量的变化导致的重新渲染,我们只需要针对不同的操作赋予不同的优先级即可。

具体来说,主要包含以下三个点:

  • 为不同操作造成的“自变量变化”赋予不同的优先级
  • 所有优先级统一调度,优先处理“最高优先级的更新”
  • 如果更新正在进行(进入虚拟 DOM 相关工作),此时有“更高优先级的更新”产生的话,中段当前的更新,优先处理高优先级更新

要实现上面的这三个点,就需要 React 底层能实现:

  • 用于调度优先级的调度器
  • 调度器对应的调度算法
  • 支持可中断的虚拟 DOM 的实现

所以不管是解决 CPU 的瓶颈还是 I/O 的瓶颈,底层的诉求都是需要实现 time slice

# 新架构的解决思路

# 解决 CPU 瓶颈

从 React v16 开始,官方团队正式引用了 Fiber 的概念,这是一种通过链表来描述 UI 的方式,本质上你也可以看作是一种虚拟 DOM 的实现。

与其将 “Virtual DOM” 视为一种技术,不如说它是一种模式,人们提到它时经常是要表达不同的东西。在 React 的世界里,术语 “Virtual DOM” 通常与 React 元素 (opens new window)关联在一起,因为它们都是代表了用户界面的对象。而 React 也使用一个名为 “fibers” 的内部对象来存放组件树的附加信息。上述二者也被认为是 React 中 “Virtual DOM” 实现的一部分。

Fiber 本质上也是一个对象,但是和之前 React 元素不同的地方在于对象之间使用链表的结构串联起来,child 指向子元素,sibling 指向兄弟元素,return 指向父元素。

如下图:

image-20230224112508425

使用链表这种结构,有一个最大的好处就是在进行整颗树的对比(reconcile)计算时,这个过程是可以被打断。

在发现一帧时间已经不够,不能够再继续执行 JS,需要渲染下一帧的时候,这个时候就会打断 JS 的执行,优先渲染下一帧。渲染完成后再接着回来完成上一次没有执行完的 JS 计算。

image-20221227150225918

官方还提供了一个 Stack 架构和 Fiber 架构的对比示例:https://claudiopro.github.io/react-fiber-vs-stack-demo/

下面是 React 源码中创建 Fiber 对象的相关代码:

const createFiber = function (tag, pendingProps, key, mode) {
  // 创建 fiber 节点的实例对象
  return new FiberNode(tag, pendingProps, key, mode);
};

function FiberNode(tag, pendingProps, key, mode) {
  // Instance
  this.tag = tag;
  this.key = key;
  this.elementType = null;
  this.type = null;
  this.stateNode = null; // 映射真实 DOM

  // Fiber
  // 上下、前后 fiber 通过链表的形式进行关联
  this.return = null;
  this.child = null;
  this.sibling = null;
  this.index = 0;

  this.ref = null;
  this.refCleanup = null;
  // 和 hook 相关
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;

  this.mode = mode;

  // Effects
  this.flags = NoFlags;
  this.subtreeFlags = NoFlags;
  this.deletions = null;

  this.lanes = NoLanes;
  this.childLanes = NoLanes;

  this.alternate = null;
  // ...
}

# 解决 I/O 瓶颈

从 React v16 开始引入了 Scheduler(调度器),用来调度任务的优先级。

UI = f(state):

  • 根据自变量的变化计算出 UI
  • 根据 UI 变化执行具体的宿主环境的 API

React v16之前:

  • Reconciler(协调器):vdom 的实现,根据自变量的变化计算出 UI 的变化
  • Renderer(渲染器):负责将 UI 的变化渲染到宿主环境

从 React v16 开始,多了一个组件:

  • Scheduler(调度器):调度任务的优先级,高优先级的任务会优先进入到 Reconciler
  • Reconciler(协调器):vdom 的实现,根据自变量的变化计算出 UI 的变化
  • Renderer(渲染器):负责将 UI 的变化渲染到宿主环境

新架构中,Reconciler 的更新流程也从之前的递归变成了“可中断的循环过程”。

function workLoopConcurrent{
  // 如果还有任务,并且时间切片还有剩余的时间
  while(workInProgress !== null && !shouldYield()){
    performUnitOfWork(workInProgress);
  }
}

function shouldYield(){
  // 当前时间是否大于过期时间
  // 其中 deadline = getCurrentTime() + yieldInterval
  // yieldInterval 为调度器预设的时间间隔,默认为 5ms
  return getCurrentTime() >= deadline;
}

每次循环都会调用 shouldYield 判断当前的时间切片是否有足够的剩余时间,如果没有足够的剩余时间,就暂停 reconciler 的执行,将主线程还给渲染流水线,进行下一帧的渲染操作,渲染工作完成后,再等待下一个宏任务进行后续代码的执行。

# 真题解答

题目:是否了解过 React 的架构?新的 Fiber 架构相较于之前的 Stack 架构有什么优势?

参考答案:

React v15及其之前的架构:

  • Reconciler(协调器):VDOM 的实现,负责根据自变量变化计算出 UI 变化
  • Renderer(渲染器):负责将 UI 变化渲染到宿主环境中

这种架构称之为 Stack 架构,在 Reconciler 中,mount 的组件会调用 mountComponent,update 的组件会调用 updateComponent,这两个方法都会递归更新子组件,更新流程一旦开始,中途无法中断。

但是随着应用规模的逐渐增大,之前的架构模式无法再满足“快速响应”这一需求,主要受限于如下两个方面:

  • CPU 瓶颈:由于 VDOM 在进行差异比较时,采用的是递归的方式,JS 计算会消耗大量的时间,从而导致动画、还有一些需要实时更新的内容产生视觉上的卡顿。
  • I/O 瓶颈:由于各种基于“自变量”变化而产生的更新任务没有优先级的概念,因此在某些更新任务(例如文本框的输入)有稍微的延迟,对于用户来讲也是非常敏感的,会让用户产生卡顿的感觉。

新的架构称之为 Fiber 架构:

  • Scheduler(调度器):调度任务的优先级,高优先级任务会优先进入到 Reconciler
  • Reconciler(协调器):VDOM 的实现,负责根据自变量变化计算出 UI 变化
  • Renderer(渲染器):负责将 UI 变化渲染到宿主环境中

首先引入了 Fiber 的概念,通过一个对象来描述一个 DOM 节点,但是和之前方案不同的地方在于,每个 Fiber 对象之间通过链表的方式来进行串联。通过 child 来指向子元素,通过 sibling 指向兄弟元素,通过 return 来指向父元素。

在新架构中,Reconciler 中的更新流程从递归变为了“可中断的循环过程”。每次循环都会调用 shouldYield 判断当前的 TimeSlice 是否有剩余时间,没有剩余时间则暂停更新流程,将主线程还给渲染流水线,等待下一个宏任务再继续执行。这样就解决了 CPU 的瓶颈问题。

另外在新架构中还引入了 Scheduler 调度器,用来调度任务的优先级,从而解决了 I/O 的瓶颈问题。

虚拟 DOM
React 渲染流程

← 虚拟 DOM React 渲染流程→

Theme by Vdoing | Copyright © 2021-2024 蜀ICP备2024068710号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式