React为什么要推出Fiber架构(Fiber架构诞生的背景)?
在React15版本之前,使用的是堆栈调和Stack Reconciler,采用同步递归的渲染方式。该方法一旦开始就不能中断,直到整个组件树都被遍历完。当组件树层级过深或更新频繁时,会导致主线程长时间被占用,引发界面卡顿和响应迟缓的问题。
为解决这一问题,React推出Fiber架构。
什么是Fiber?
Fiber是React内部的最小渲染单元,每个组件对应一个Fiber节点。Fiber节点保存组件的类型、状态、子节点、父节点等信息,并记录当前任务的进度和优先级。
{
type: 'h1', // 组件类型
key: null, // React key
props: { ... }, // 输入的props
state: { ... }, // 组件的state (如果是class组件或带有state的function组件)
child: Fiber | null, // 第一个子元素的Fiber
sibling: Fiber | null, // 下一个兄弟元素的Fiber
return: Fiber | null, // 父元素的Fiber
// ...其他属性
}
Fiber架构中有两个特性:
-
1.
动态优先级。动态优先级会给每个任务分配一个权重值,表示它的顺序,react在协调过程中会动态调整任务的执行顺序,优先执行比较高的任务,后执行低的任务。 -
2.
可中断渲染,允许react渲染过程中对任务暂停,适当的时机再重启,解决了在传统机制中渲染过程是单次的,执行不停止,从而引发的渲染阻塞,导致页面卡顿的问题。在react Fiber中渲染是分帧进行的,当react在执行任务中,如果发现某个任务的执行时间超过当前帧的剩余时间,会暂停。浏览器执行UI渲染,下一帧继续执行没有执行完成的任务。避免UI阻塞问题。
Fiber分为几个阶段:
-
调度阶段调度阶段是React更新数据生成新的virtual DOM的过程,在这个阶段,React会通过Diff算法找出需要更新的元素,放入更新队列中。
这个阶段可以中断。因为React会将任务拆分成多个小块,每个小块执行完毕后可以暂停,等待高优先级任务的处理。 -
渲染阶段渲染阶段是React遍历更新队列,将变更应用到DOM上的过程。
这个阶段也可以中断,因为React会构建新的Fiber树,与当前树并行存在,通过双缓冲机制,React可以在Fiber树上进行渲染和计算,不影响当前DOM的渲染。- 双缓存机制:React使用双缓存机制管理Fiber树的更新。包括“current”树和“workInprogress”树。当前显示在屏幕上的UI对应于“current”树,正在构建的新UI树对应于“workInprogress”树,当“workInprogress”树构建完成后,替换“current”树,实现UI的更新。
-
提交阶段提交阶段是React执行工作循环的地方,在这个阶段,React会不断检查事件片,如果时间片结束或遇到高优先级任务,
则会中断当前任务,工作循环会不断重复,直到任务完成。
React Fiber的工作原理
1:构建 Fiber 树
React Fiber 会创建一棵 Fiber 树,用于表示React组件树的结构和状态。Fiber 树是一个轻量级的树形结构,与 React 组件树一一对应。与传统的递归遍历不同,React Fiber 采用链表结构对树进行分片拆分,实现递增渲染的效果。
2:确定调度优先级
在 Fiber 树构建完成后,React Fiber 会根据组件的更新状态和优先级,确定需要优先更新的组件,即“调度”更新。React Fiber 支持多个优先级,组件的优先级由组件的更新情况和所处的位置决定。比如页面的顶部和底部可以具有不同的优先级,用户的交互行为比自动更新的优先级更高,等等。
3:执行调度更新
当确定了需要调度更新的组件后,React Fiber 会将这些组件标记为“脏”(dirty),并将它们放入更新队列中,待后续处理。需要注意的是,React Fiber 并未立即执行更新操作,而是等待时间片到来时才开始执行,这样可以让 React Fiber 在执行更新时具有更高的优先级,提高了应用的响应性和性能。
4:中断和恢复
在执行更新时,如果需要中断当前任务,React Fiber 可以根据当前任务的优先级、执行时间和剩余时间等因素,自动中断当前任务,并将现场保存到堆栈中。当下次处理到该任务的时候,React Fiber 可以通过恢复堆栈中保存的现场信息,继续执行任务,从而实现中断和恢复的效果。
5:渲染和提交
React Fiber 会将更新结果渲染到页面中,并设置下一次更新的时间和优先级。React Fiber 利用 WebGL 和 canvas 等浏览器原生的绘制 API,实现了GPU加速,从而提高了渲染效率和性能。
Vue的Scheduler原理与React的Scheduler的区别?
-
Vue 的 Scheduler 是通过 Promise 创建微任务来实现的,并在微任务中批量进行所有更新任务
-
React 的 Scheduler 是通过 MessageChannel API 创建宏任务来实现的,并通过时间切片在多个事件循环中分批执行更新任务
React架构发展历程:
React15架构可以分为两层:
- Reconciler(协调器)—— 负责找出变化的组件;
- 调用组件的render方法,将返回的JSX 转化为虚拟的DOM
- 将虚拟的DOM和上次更新时的虚拟DOM对比
- 对比找出本次更新中变化的虚拟DOM
- 通知Renderer将变化的虚拟DOM渲染到页面上
- Renderer(渲染器)—— 负责将变化的组件渲染到页面上;
React16架构可以分为三层:
- Scheduler(调度器)—— 调度任务的优先级,高优任务优先进入Reconciler;
- Reconciler(协调器)—— 负责找出变化的组件:更新工作从递归变成了可以中断的循环过程。Reconciler内部采用了Fiber的架构;
- Renderer(渲染器)—— 负责将变化的组件渲染到页面上。
Scheduler主要负责两件事:
- 1:在浏览器主线程空闲时执行任务。
- 2:执行任务直到时间片用完或任务执行完成,让出主线程,等待下一次主线程空闲再执行任务。
Scheduler的功能:时间切片 & 优先级调度
时间切片:把原来长时间的任务划分成多个执行时间短的小任务。优先级调度:更新任务的优先级有高有低,优先执行高优先级任务(用户交互相关的),低优先级任务(如后台数据处理)则可以延后或被中断。[React使用Lanes模型管理不同优先级]
时间切片
Fiber利用浏览器的requestIdleCallback API 实现时间切片。
总结:
1:在浏览器环境下使用MessageChannel API,实现在浏览器空闲时执行任务。
2:任务按过期时间排序,循环执行taskQueue中的任务,直到没有过期任务,或者时间片用尽。否则退出循环并让出线程,在下一个时间循环中再执行任务。
Reconciler(协调)有两个核心阶段:render阶段和commit阶段
render阶段
React的render阶段是组件渲染更新的重要环节,主要负责生成新的Fiber树(也称为“工作树”或“alternate树”),这个树反映了应用即将更新的状态。render阶段的执行过程可以分为以下几个主要步骤:
-
开始阶段(Begin Phase) :
- React会为当前渲染的组件创建一个Fiber节点,并初始化一些基本属性。
- 设置工作循环的上下文,包括当前的工作Fiber节点、根节点等。
-
渲染阶段(Render Phase) :
-
调和(Reconciliation) :React会遍历旧的Fiber树(当前树),并根据新的虚拟DOM(React元素)生成新的Fiber树。这个过程称为“调和”或“协调”。
- 对于每个组件,React会调用其渲染方法(如
render或函数组件本身)来获取新的子元素。 - React会根据新的子元素和旧的Fiber节点进行比较,决定如何更新Fiber树。这个过程包括创建新的Fiber节点、复用旧的Fiber节点或删除不再需要的Fiber节点。
- 在这个过程中,React还会为每个Fiber节点标记副作用(effect tag),如Placement(插入)、Update(更新)、Deletion(删除)等,以便在后续的提交阶段执行相应的DOM操作。
- 对于每个组件,React会调用其渲染方法(如
-
-
完成阶段(Complete Phase) :
- 当React完成一个Fiber节点的所有子节点的调和后,会进入该节点的完成阶段。
- 在完成阶段,React会处理一些收尾工作,如收集副作用、更新节点的状态等。
- 完成阶段的处理会从子节点向上冒泡,直到根节点。
-
中断与恢复:
- Fiber架构的一个重要特性是能够中断和恢复渲染过程。如果React在渲染过程中发现没有足够的时间完成当前任务,它会将控制权交回给浏览器,让浏览器处理其他任务,如用户交互、渲染等。当主线程空闲时,React会恢复渲染过程。
-
生成副作用列表:
- 在整个render阶段,React会构建一个副作用列表(effect list),这个列表包含了所有需要执行副作用的Fiber节点。副作用列表会在后续的提交阶段使用,以执行实际的DOM更新。 render阶段完成后,React会进入提交阶段(Commit Phase),在这个阶段,React会根据副作用列表执行实际的DOM操作,如插入、更新和删除节点。提交阶段是同步执行的,以确保DOM状态的完整性。 需要注意的是,render阶段的工作是在内存中进行的,不会直接操作DOM,这样可以确保在执行实际的DOM更新之前,所有的变更都已经计算完毕,从而提高性能和减少页面重绘和回流。
commite阶段
React的commit阶段是组件渲染更新的最后一步,主要负责将render阶段生成的副作用应用到实际的DOM上。这个过程是同步执行的,以确保UI的稳定性和一致性。commit阶段可以分为以下几个主要步骤:
-
_beforeMutation阶段:
-
在这个阶段,React会执行一些DOM变更前的准备工作,例如:
- 调用
getSnapshotBeforeUpdate生命周期方法(在类组件中)。 - 执行
useEffect回调函数中返回的清理函数(在函数组件中)。 - 保持屏幕上的内容不变,直到所有DOM变更准备就绪。
- 调用
-
-
mutation阶段:
-
这个阶段是commit阶段的核心,React会根据render阶段收集的副作用列表(effect list)执行实际的DOM操作。这些操作包括:
- 插入(Placement) :将新的DOM节点插入到相应的位置。
- 更新(Update) :更新DOM节点的属性,如样式、属性等。
- 删除(Deletion) :移除不再需要的DOM节点。
- 替换(Replacement) :替换现有的DOM节点。
-
除了DOM操作,React还会更新refs,确保它们指向正确的实例。
-
-
layout阶段:
-
在这个阶段,React会执行一些DOM变更后的工作,例如:
- 调用
componentDidMount和componentDidUpdate生命周期方法(在类组件中)。 - 调用
useLayoutEffect的回调函数(在函数组件中)。 -/layout阶段是同步执行的,因为它可能会读取DOM布局并作出相应的调整。
- 调用
-
-
_afterPaint阶段:
-
这个阶段是commit阶段的最后一步,它是在浏览器完成绘制之后执行的。在这个阶段,React会执行以下操作:
- 调用
useEffect的回调函数(在函数组件中),这些回调函数不依赖于DOM的布局。 - 清理一些内部状态,如重置悬挂状态( suspense)。 在整个commit阶段,React会确保所有副作用都按顺序执行,并且保持DOM的稳定性和一致性。由于commit阶段是同步的,它可能会阻塞主线程,因此React会在render阶段尽可能减少需要在此阶段执行的工作,以减少对性能的影响。 需要注意的是,commit阶段的操作是直接对DOM进行的,因此任何在commit阶段执行的代码都应该避免进行大量的计算或者重新触发新的渲染,以避免造成性能问题。
- 调用
-
React,Fiber如何实现时间切片?
在React中,Fiber架构通过时间切片(Time Slicing)来实现任务的异步执行,从而避免长时间占用主线程,提高应用的响应性和性能。时间切片的实现主要依赖于以下机制:
-
任务分解:
- Fiber将整个渲染过程分解为许多小的任务单元,每个任务单元对应一个Fiber节点。这样,React可以将大的渲染任务分解为多个小任务,每个小任务都可以独立执行和暂停。
-
请求Idle时间:
- React使用
requestIdleCallbackAPI(在浏览器不支持的情况下,使用polyfill或自定义实现)来请求浏览器在主线程空闲时执行任务。requestIdleCallback允许开发者指定一个回调函数,该函数将在浏览器空闲时被调用,并且可以设置超时时间,以确保回调函数在一定时间内被执行。
- React使用
-
工作循环(Work Loop) :
- Fiber通过工作循环来管理任务的执行。工作循环会不断地从任务队列中取出任务并执行,每个任务执行完成后,会检查剩余的时间是否足够继续执行下一个任务。如果时间不足,React会将控制权交回给浏览器,让浏览器处理其他高优先级任务,如用户输入、动画等。
-
中断与恢复:
- 在执行任务的过程中,如果遇到时间不足或者有更高优先级的任务需要处理,React可以中断当前任务的执行,并在稍后恢复。每个Fiber节点都会保存其执行状态,以便在恢复时能够从上次中断的地方继续执行。
-
优先级调度:
- React为不同的任务分配不同的优先级,高优先级的任务(如用户交互)会优先执行。优先级调度确保了重要的任务能够得到及时处理,而低优先级的任务可以在浏览器空闲时执行。
-
协作式多任务:
- Fiber的时间切片机制是一种协作式多任务处理方式。React与浏览器协作,确保在执行JavaScript任务的同时,浏览器仍有足够的时间处理其他任务,如渲染、绘制和执行其他脚本。 通过这些机制,React的Fiber架构实现了时间切片,使得长时间运行的渲染任务可以被分解为多个小任务,这些任务可以在浏览器的空闲时间分散执行,从而避免了长时间占用主线程,提高了应用的响应性和性能。 需要注意的是,React的时间切片机制主要是为了优化大量计算的渲染过程,对于DOM操作等无法分割的任务,React仍然会在commit阶段同步执行,以确保DOM的稳定性和一致性。