- React 核心机制是跟踪组件的状态变化,并将更新的状态映射到新的界面,这个过程称为协调。
- React Fiber 是React核心算法的一次重新实现
从问题来理解为什么react需要Fiber
Q1:React的设计理念是构建快速响应的大型Web应用程序。制约快速响应的因素?反映算法不足
- CPU瓶颈:大量的同步计算任务阻塞了浏览器的UI渲染,造成页面卡顿。而 React 的 Reconcilation 是 CPU 密集型的操作,React 15使用的Virtual DOM协调算法(因为它在内部使用堆栈也被称为堆栈协调器),共享一个协调器,可能会导致混淆。
- IO的瓶颈:网络请求响应不及时,造成白屏
Q2:Vue3 动静结合的DOM Diff 与 React的对比?反映编译时优化不足
- Vue3的DOM Diff在预编译进行优化,在预编译阶段静态分析模版,分析出视图依赖了哪些数据,进行响应式处理。Template 模板是一种非常有约束的语言,你只能以某种方式去编写模板。
- 而React 是局部渲染,无法从模板层面进行静态分析。JSX 具有 JavaScript 的完整表现力,可以构建非常复杂的组件。但是灵活的语法,也意味着引擎难以理解,无法预判开发者的用户意图,从而难以优化性能。
React 架构
- 调度器(Scheduler):调度任务优先级,高级任务优先进入协调器,不使用requestIdleCallback(兼容性不好)。
- 协调器(Reconciler):负责找出更改的组件,更新工作从递归变成可以中断的循环过程。内部采用Fiber架构
- 渲染器(Renderer):将变化的组件渲染到页面上
调度器
- 以浏览器是否有剩余时间作为任务中断的标准,当浏览器有剩余时间时通知我们。
协调器
- 更新工作从15递归变成了16+可以中断的循环过程。每次循环都会调用shouldYield判断当前是否有剩余时间。
- 当Scheduler将任务交给Reconciler后,Reconciler会为变化的虚拟 DOM 打上代表增/删/更新的标记
- 整个Scheduler与Reconciler的工作都在内存中进行。只有当所有组件都完成Reconciler的工作,才会统一交给Renderer。
渲染器
- 根据Reconciler为虚拟 DOM 打的标记,同步执行对应的 DOM 操作
Fiber
- 本质是『虚拟DOM』。Fiber节点可以保存对应的DOM节点,Fiber节点构成的Fiber树就对应DOM树
- 从数据结构看,每个Fiber节点对应一个React Element,保存了该组件的类型(函数组件、类组件、原生组件)、对应的DOM节点等信息
- 从工作单元看,每个Fiber节点保存了本次更新中该组件改变的状态、要执行的工作(删除、插入、更新)
Fiber 树
- 特点:链表结构,将协调的『递归遍历』改为『循环遍历』
- React 第一次渲染时,会通过 React.createElement 创建一颗 Element 树,可以称之为 Virtual DOM Tree,由于要记录上下文信息,加入了 Fiber,每一个 Element 会对应一个 Fiber Node,将 Fiber Node 链接起来的结构成为 Fiber Tree。
- 多个节点如何连接成树?
1
2
3this.return = null // 指向父Fiber节点
this.child = null // 指向子Fiber节点
this.sibling = null // 指向右边第一个兄弟Fiber节点
Fiber 架构的工作原理
- 双缓存:在内存中构建并直接替换
- 双缓存树:在React中最多会同时存在两棵Fiber树。
- 当前屏幕显示内容对应的Fiber树称为current Fiber树(即上次渲染构建的Fiber树)。
- 正在内存中构建的Fiber树称为workInProgress Fiber树。(无论是创建还是更新、挂起、恢复、终止操作都是发生在W树创建过程中。W树构建过程其实就是循环的执行任务和创建下一个任务。)
初次渲染的构建流程
- 创建阶段:首次执行ReactDOM.render 会创建 fiberRootNode(整个应用的根节点)和 rootFiber(
<App />
所在组件树的根节点)。由于首屏渲染,页面没有挂载任何DOM,所以fiberRootNode.current指向的rootFiber没有任何子Fiber节点(即current Fiber树为空)。 - render阶段:进入render阶段,根据组件返回的JSX载内存中一次创建Fiber节点并连接在一起构建Fiber树,被称为『workInProgress Fiber树』。在构建W树时会尝试复用C树中已有的Fiber节点内的属性。在首屏渲染时,只有rootFiber存在对应的current Fiber(即rootFiber.alternate)
- commit阶段:进入commit阶段,已构建完的workInProgress Fiber树渲染到页面上
更新阶段的构建流程
- 开启新一轮的render阶段并构建新一颗W树。创建时复用C树的对应节点数据
- 已构建完的W树渲染到页面上。渲染完成W树变成C树。
JSX & Fiber节点
- 在组件mount时,Reconciler『根据JSX描述』的组件内容生成组件对应的『Fiber节点』。
- 在update时,Reconciler将JSX与Fiber节点保存的『数据对比』,生成组件对应的Fiber节点,并根据对比结果为Fiber节点打上标记。
React Fiber 执行过程
任务拆分
- 在调和阶段递归遍历 VDOM 这个大任务分成若干小任务,每个任务只负责一个节点的处理。
任务挂起、恢复、终止
- 当当前分配的任务完成后,先判断这一帧是否还有空闲时间,没有就挂起下一个任务的执行,记住当前挂起的节点,让出控制权给浏览器执行更高优先级任务
任务恢复
- 在浏览器渲染完一帧后,判断当前帧是否有剩余时间,如果有就恢复执行之前挂起的任务。如果没有任务需要处理,代表调和阶段完成,可以开始进入渲染阶段。
- 判断一帧是否用空闲时间,使用 RequestIdleCallback
- 恢复执行时通过链表获取下一个任务
任务终止
- 不是每次更新都会走到提交阶段。当在调和过程中触发了新的更新,在执行下一个任务的时候,判断是否有优先级更高的执行任务,如果有就终止原来将要执行的任务,开始新的 workInProgressFiber 树构建过程,开始新的更新流程。这样可以避免重复更新操作。这也是在 React 16 以后生命周期函数 componentWillMount 有可能会执行多次的原因。
任务具备优先级
- 在任务执行过程中收集到的每个FiberNode的副作用,形成的副作用链表。到commit阶段时,直接遍历副作用链完成DOM更新。更新DOM的过程不可中断。
- 任务优先级有六种
- synchronous:与之前的堆栈协调器操作一样,同步执行
- task:在next tick之前执行
- animation:下一帧之前执行
- high:在不久的将来立即执行
- low:稍微延迟执行也没关系
- offscreen:下一次render 或scroll 时执行