React Fiber架构的原理和工作模式
React Fiber架构的原理和工作模式
React在16版本中引入了Fiber架构,这是React内部工作原理的一次重大革新。Fiber架构解决了React在处理复杂组件树时的性能问题,使得React应用能够更加流畅地响应用户输入。本文将深入探讨Fiber架构的原理和工作模式,帮助读者理解React是如何实现高效渲染和更新的。
什么是Fiber?
当编写React组件并使用JSX时,React在底层会将JSX转换为元素的对象结构。这个转换过程是由Babel或类似JS编译器处理的。
JSX的本质
JSX是JavaScript的语法扩展,看起来像HTML,但实际上它是JavaScript代码。在React中,JSX会被转换为标准的JavaScript函数调用,即React.createElement()
。
转换机制
React.createElement
是React提供的核心函数,用来创建虚拟DOM(React Element)。
- 第一个参数:创建的HTML标签,例如
h1
- 第二个参数:属性对象props,如果没有属性则为
null
- 第三个参数:标签包含的内容,例如
'Hello, world'
Babel使用插件(如@babel/plugin-transform-react-jsx
)完成这个转换:
{
"presets": ["@babel/preset-react"]
}
运行完Babel之后,输出的代码就是React.createElement
形式。
Babel对JSX的转换逻辑
React.createElement
的作用是构建虚拟DOM树,即一个轻量级的JavaScript对象,用来描述真实的DOM结构。React会根据这个结构最终生成并更新真实的DOM。
- JSX是语法糖,它最终被编译为
React.createElement
调用。 React.createElement
的核心作用是生成描述UI的虚拟DOM。- Babel等工具负责处理JSX的转换,使开发更直观高效。
虚拟DOM是React根据JSX创建的一种内部实例,用来追踪该组件的所有信息和状态。在Fiber架构中,这种内部实例被称为Fiber。
Fiber是一个JavaScript对象,代表React的一个工作单元,用来追踪对应组件的所有信息和状态。
例如,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工作。如果主线程有其他工作(如响应用户输入),React可以随时终止当前工作并返回执行主线程上的任务。Fiber不仅是代表组件的一个JavaScript对象,也是React调度和更新机制的核心。
为什么需要Fiber?
在React 16之前的版本中,React使用递归的方式处理组件更新,称为堆栈调和(Stack Reconciliation)。这种方法一旦开始就无法中断,必须遍历完整个组件树。当遇到大量数据或复杂视图组件时,这可能导致主线程阻塞,React应用无法即时响应用户输入或其他高优先级任务的执行。
React 16引入的Fiber架构解决了上述问题。Fiber是React创建的一个带有链接关系的DOM树,每个Fiber代表一个工作单元。React在处理任何Fiber之前,会判断当前是否有足够时间完成该工作,并可以在必要时中断当前工作。
React 16之前版本的组件更新代码示例:
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元素的类型,例如 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的工作流程
Fiber的工作流程分为两个阶段:调和(Reconciliation)和提交(Commit)。
第一阶段:调和(Reconciliation)
目标:确定哪些部分的UI需要更新。
原理:这是React构建工作进度树的阶段,会比较新的props和旧的Fiber树来确定哪些部分需要更新。
调和细分为两个小阶段:
- 创建与标记更新节点:
beginWork
- 判断Fiber节点是否要更新
- 判断Fiber子节点是更新还是复用
- 收集副作用列表:
completeUnitOfWork
和completeWork
第二阶段:提交(Commit)
目标:更新DOM并执行任何副作用。
原理:遍历在Reconciliation阶段创建的副作用列表进行更新。
在源码中,commitRoot
和commitRootImpl
是提交阶段的入口方法。提交阶段也有三个核心小阶段:
- 遍历副作用列表:
BeforeMutation
- 正式提交:
CommitMutation
- 处理layout effects:
commitLayout
从源码中可以看出,一旦进入提交阶段后,React是无法中断的。