React Fiber架构的原理和工作模式
React16 => React引入Fiber架构,解决过去更新机制的问题 =》 在长时间的更新过程中,主线程会被阻塞,导致应用无法即时响应用户输入。
核心内容: 【Fiber是什么】【Fiber的底层原理】
Fiber是什么?
当编写React组件并使用JSX时,React在底层会将JSX转换为元素的对象结构
转换的过程是一个工具链完成,由Babel或类似JS编译器处理JSX的编译工作
-
JSX的本质
JSX是JS的语法扩展,和HTML有点像,但实际是JS代码,在React中,JSX会被转换为标准的JS函数调用,即React.createElement() -
转换机制
-
React.createElement是React提供的核心函数,用来创建虚拟DOM(React Element)
-
第一个参数:创建的HTML标签,eg: h1
-
第二个参数:属性对象 props, 上图无属性即null
-
第三个参数:标签包含的内容,eg: 'Hello, world'
Babel使用插件 eg: @babel/plugin-transform-react-jsx完成转换
{
"presets": ["@babel/preset-react"]
}
运行完babel之后,输出的代码就是React.createElement形式
Babel对JSX的转换逻辑
React.createElement作用:【构建虚拟DOM树】
React使用React.createElement来构建虚拟DOM树,即一个轻量级的JS对象,用来描述真实的DOM结构
虚拟 DOM 的结构,React 会根据这个结构最终生成并更新真实的 DOM。
- JSX 是语法糖,它最终被编译为 React.createElement 调用。
- React.createElement 的核心作用是生成描述 UI 的虚拟 DOM。
- Babel 等工具负责处理 JSX 的转换,使开发更直观高效。
虚拟DOM是React根据JSX创建一种内部实例,用来追踪该组件的所有信息和状态。
早期称之为实例或虚拟DOM对象,在Fiber架构中,称之为【Fiber】
Fiber是一个JS对象,代表React的一个工作单元,用来追踪对应组件的所有信息和状态
Eg: Fiber对象结构
{
type: 'h1', // 组件类型(标签类型
key: null, // React key
props: {}, // 输入的props
state: {}, // 组件的state
child: Fiber | null, // 第一个子元素的Fiber
sibling: Fiber | null, // 下一个兄弟元素的Fiber
return: Fiber | null, // 父元素的Fiber
... 其他属性
}
React的工作是沿着Fiber树进行,试图完成每个Fiber工作,eg: 比较新旧props决定是否更新组件等
如果主线程有工作(eg: 响应用户输入)则React可随时终止当前工作并返回执行主线程上的任务。
Fiber不仅是代表组件的一个JS对象也是React调度和更新机制的核心
为啥要Fiber???
React16之前的版本,使用递归的方式处理组件更新,称之为堆栈调和(Stack Reconciliation)
该方法一旦开始无法中断,必须直到整个组件树都遍历完。
当遇到大量数据或负责视图组件时可能导致主线程阻塞,React应用无法即时响应用户输入或其他高优先级任务的执行。
React16 Fiber的引入解决了上述问题, Fiber是React创建的一个带有链接关系的DOM树 每个Fiber代表一个工作单元,React处理任何Fiber之前,会判断当前是否有足够时间完成该工作,并可以在必要时中断当前工作。
React16之前版本采用的循环递归更新组件
function render(element, rootParent) {
console.log('output-> render 执行',)
// 根据元素类型创建节点
let dom = document.createElement(element.type)
rootParent.appendChild(dom)
// 添加dom的属性
Object.keys(element.props)
.filter(prop => prop !== 'children')
.forEach(attr => {
dom[attr] = element.props[attr]
})
// 将子元素进行渲染
if(Array.isArray(element.props.children)) {
element.props.children.forEach(child => {
render(child, dom)
})
} else {
dom.innerHTML = element.props.children
}
}
Fiber的结构
function FiberNode(
this: $FlowFixMe,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// 基本属性
this.tag = tag; // 描述此Fiber的启动模式的值(LegacyRoot=0;ConcurrentRoot=1)
this.key = key; // React key
this.elementType = null; // 描述React元素的类型,eg: JSX<App/> 则elementType是App
this.type = null; // 组件类型
this.stateNode = null; // 对于类组件,这是类组件的实例;对于DOM元素,是对应的DOM节点
// Fiber链接
this.child = null; // 指向第一个子Fiber
this.sibling = null; // 指向其兄弟Fiber
this.return = null; // 指向父Fiber
this.index = 0; // 子Fiber中的索引位置
this.ref = null; // 如果组件上有ref属性则该属性指向它
this.refCleanup = null; // 如果组件上的ref属性在更新中被删除或更改,此字段会用于追踪需要清理的旧ref
// Props & State
this.pendingProps = pendingProps; // 正在等待处理的新的props
this.memoizedProps = null; // 上一次渲染时的props
this.updateQueue = null; // 一个队列,包含了该Fiber上的状态更新和副作用
this.memoizedState = null; // 上一次渲染时的state
this.dependencies = null; // 该Fiber订阅的上下文或其他资源的描述
// 工作模式
this.mode = mode; // 描述Fiber工作模式的标志(eg: Concurrent模式、Blocking模式等
// Effects
this.flags = NoFlags; // 该Fiber发生的副作用标志
this.subtreeFlags = NoFlags; // 描述该Fiber子树中发生的副作用的标志
this.deletions = null; // 在commit阶段要删除的子Fiber数组
this.lanes = NoLanes; // 与React的并发模式有关的调度概念。
this.childLanes = NoLanes; // 与React的并发模式有关的调度概念。
this.alternate = null; // Current Tree和Work-in-progress (WIP) Tree的互相指向对方tree里的对应单元
}
Fiber是一个更加强大的虚拟DOM
Fiber工作原理
Fiber工作原理最核心的:可以中断和恢复,该特性增强了React的【并发性和响应性】
实现可中断和恢复的原因: Fiber的提供的信息让React可以追踪工作进度、管理调度和同步更新到DOM
Fiber工作原理的关键点:
-
工作单元:每个Fiber节点代表一个工作单元,所有Fiber节点共同组成一个Fiber链表树(有链接属性和树的结构),让React可细粒度控制节点的行为
-
链接属性:child、sibling和return字段构成Fiber之间的链接关系,使得React能够遍历组件树并知道从哪里开始、继续或停止工作
-
双双缓冲技术:React在更新时,会根据现有的Fiber树(Current Tree)创建一个新的临时树(Work-in-progress (WIP) Tree),WIP-Tree包含了当前更新受影响的最高节点直至其所有子孙节点。Current Tree是当前显示在页面上的视图,WIP-Tree则是在后台进行更新,WIP-Tree更新完成后会复制其它节点,并最终替换掉Current Tree,成为新的Current Tree。因为React在更新时总是维护了两个Fiber树,所以可以随时进行比较、中断或恢复等操作,而且这种机制让React能够同时具备拥有优秀的渲染性能和UI的稳定性。
-
State和Props: memoizedProps、pendingProps 和 memoizedState 字段让React知道组件的上一个状态和即将应用的状态。通过比较这些值,React可以决定组件是否需要更新,从而避免不必要的渲染,提高性能。
-
副作用的追踪:flags 和 subtreeFlags 字段标识Fiber及其子树中需要执行的副作用,例如DOM更新、生命周期方法调用等。React会积累这些副作用,然后在Commit阶段一次性执行,从而提高效率。
Fiber的工作流程 分为两个阶段: 【Reconciliation-调和】【Commit-提交】
第一 调和
目标: 确定哪些部分的UI需要更新。
原理: 这是React构建工作进度树的阶段,会比较新的props和旧的Fiber树来确定哪些部分需要更新。
调和细分为2个小阶段
- 创建与标记更新节点:beginWork
- 判断Fiber节点是否要更新:
- 判断Fiber子节点是更新还是复用:
- 收集副作用列表:completeUnitOfWork和completeWork
第二 提交
目标: 更新DOM并执行任何副作用。
原理: 遍历在Reconciliation阶段创建的副作用列表进行更新。
码里 commitRoot 和 commitRootImpl 是提交阶段的入口方法,在两个方法中,可以看出来提交阶段也有三个核心小阶段
- 遍历副作用列表:BeforeMutation
- 正式提交:CommitMutation
- 处理layout effects:commitLayout
从源码里我们可以看到,一旦进入提交阶段后,React是无法中断的。
https://weijunext.com/article/dive-into-react-fiber#第二阶段:Commit(提交)