- Published on
React Fiber架构
背景
React从16开始,架构可以分为三层:
- Scheduler(调度器)—— 调度任务的优先级,高优任务优先进入Reconciler
- Reconciler(协调器)—— 负责找出变化的组件,Render阶段
- Renderer(渲染器)—— 负责将变化的组件渲染到页面上,Commit阶段
Render阶段和Commit阶段就是 React 展示页面的过程,本文暂时忽略 Scheduler的工作。
整体执行流程
- JSX首先被转化成了
Fiber
节点 - React运行时代码处理的是
Fiber
节点 - 从 fiberRootNode 节点开始,以
Fiber
节点为基本单元,循环遍历,生成整棵fiber tree
(Render阶段) - 遍历
fiber tree
,处理DOM节点,执行生命周期 ,切换wip tree和current tree(Commit阶段)
Fiber节点
在React15
及以前,Reconciler
采用递归的方式创建虚拟DOM,递归过程是不能中断的。如果组件树的层级很深,递归会占用线程很多时间,造成卡顿。 为了解决这个问题,React16
将递归的无法中断的更新重构为异步的可中断更新,即基于Fiber节点的循环,由于曾经用于递归的虚拟DOM数据结构已经无法满足需要。于是,全新的Fiber
架构应运而生。
Fiber
包含三层含义:
- 作为架构来说,每个
Fiber节点
连接而成,构成了整个页面的Fiber Tree
。 - 作为静态的数据结构来说,每个
Fiber节点
对应一个React element
,保存了该组件的类型(函数组件/类组件/原生组件...)、对应的DOM节点等信息。 - 作为动态的工作单元来说,每个
Fiber节点
保存了本次更新中该组件改变的状态、要执行的工作(需要被删除/被插入页面中/被更新...)。
React
源码中表示如下:
function FiberNode(tag, pendingProps, key, mode) {
// Instance
// 作为静态数据结构的属性
this.tag = tag; // Fiber对应组件的类型 Function/Class/Host...
this.key = key;
this.elementType = null; // 对于 FunctionComponent,指函数本身,对于ClassComponent,指class,对于HostComponent,指DOM节点tagName
this.type = null;
this.stateNode = null; // Fiber
// 用于连接其他Fiber节点形成Fiber树
this.return = null; // 指向父级Fiber节点
this.child = null; // 指向第一个子Fiber节点
this.sibling = null; // 指向右边第一个兄弟Fiber节点
this.index = 0;
this.ref = null;
// 作为动态的工作单元的属性
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;
// 指向该fiber在另一次更新时对应的fiber current fiber, wip fiber
this.alternate = null;
...
}
举一个React组件的例子:
function App() {
return (
<div>
hello
<span>world</span>
</div>
)
}
该组件对应的Fiber
架构如下图:

可以看到 Fiber tree
是一个特殊的链表结构,父通过child
指针连接第一个子结点,兄弟结点通过 sibling
结点单向连接,每个子结点都有一个return
指针指向父结点。
React双缓存Fiber树
在React整个App运行期间中最多会同时存在两棵Fiber
树。当前屏幕上显示内容对应的Fiber树
称为current Fiber tree(current tree
),正在内存中构建的Fiber树
称为 workInProgress Fiber tree(wip tree
)。
current fiber
:current Fiber树
中的Fiber节点
workInProgress fiber
:workInProgress Fiber树
中的Fiber节点
current fiber
和workInProgress fiber
通过alternate属性连接
React
应用的根节点通过使current
指针指向不同Fiber树
的rootFiber
来完成current Fiber
树的切换。
即当workInProgress Fiber树
构建完成交给Renderer
渲染(commit阶段)在页面上后,应用根节点的current
指针指向workInProgress Fiber树
,此时旧的workInProgress Fiber树
就变为新的current Fiber树
。
每次状态更新都会更新workInProgress Fiber树
,通过current tree
与workInProgress tree
的交换,完成DOM
更新。
举如下一个React组件的例子来继续说明:
// index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
function App() {
const [count, setCount] = useState(0)
return <div onClick={() => setCount(count + 1)}>{num}</div>
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
mount时
- app首次执行时, ReactDOM.createRoot 会创建 fiberRootNode 和 rootFiber。
fiberRootNode
:整个应用的根节点rootFiber
:<App/>
(根组件)所在组件树的根节点fiberRootNode
的current
会指向当前页面上已渲染内容对应Fiber树
,即current Fiber树
。
function createFiberRoot() {
var root = new FiberRootNode(containerInfo, tag, hydrate, identifierPrefix, onRecoverableError);
// containerInfo 就是dom根节点
var uninitializedFiber = createHostRootFiber(tag, isStrictMode);
root.current = uninitializedFiber; // root.current指向的是rootFiber,是<App />所在的组件树的根节点
uninitializedFiber.stateNode = root; // 循环引用
// 此时,uninitializedFiber的child,alrernate,sibling,return及其他属性都是null,还没有遍历jsx生成fiber tree
// root还是pure fiberRootNode(no render(), unmount())
var fiberRootNode = new ReactDOMRoot(root); // root添加了render(), unmount()
return fiberRootNode
}
function ReactDOMRoot(internalRoot) {
this._internalRoot = internalRoot;
}
// root.render(<App /) 对应的代码
ReactDOMRoot.prototype.render = function (children) {
var root = this._internalRoot;
// children 是<App />对应的React.Element
// root就是fiberRootNode
updateContainer(children, root, null, null)
...
}
ReactDOMRoot.prototype.unmount = function () {
var root = this._internalRoot;
...
}
由于是首屏渲染,页面中没有任何DOM,所以 rootFiber 没有任何子Fiber节点
- 进入 Render 阶段,根据组件返回的JSX在内存中构建
wip fiber tree
。
构建wip tree会尝试复用current fiber中的Fiber节点,首屏渲染时,wip fiber tree中只有 rootFiber
存在对应的 current fiber
节点(rootFiber.alternate
)。
下图中右侧即为wip fiber tree:

- 上图右侧
wip tree
在commit
阶段渲染到页面上。
此时页面DOM
更新为右侧tree对应的dom。fiberRootNode
的current
指向wip tree
,使其变为current tree
。
update时
- 点击div节点触发状态改变,开启新的 render 阶段,构建一棵新的 wip tree。
- wip tree在 commit 阶段渲染到页面上(更新dom)。更新dom后,wip tree变为current tree。
render阶段
render阶段的主要工作就是生成新的wip tree
,这一过程中会沿着链表深度优先遍历,往返遍历两遍wip Fiber
节点,第一遍遍历对每个Fiber
节点执行beginWork
,第二遍遍历对每个Fiber节点执行completeWork
。
如下图,绿色箭头是beginWork
的执行路径,红色箭头是completeWork
的执行路径。

render阶段初始化
如前所述,app第一次渲染时,render阶段开始前,整个页面的fiber tree如下图,所以在开启遍历前还有一些工作,这些工作可以概括为 root.current.alernate = createFiber(root.curent)
生成wip tree
的rootFiber
。


源码如下:
// root.render(<App /) 对应的代码
ReactDOMRoot.prototype.render = function (children) {
var root = this._internalRoot;
// children 是<App />对应的React.Element
// root就是fiberRootNode
updateContainer(children, root, null, null)
...
}
ReactDOMRoot.prototype.unmount = function () {
var root = this._internalRoot;
...
}
var workInProgress; // workInProgress代表当前已创建的workInProgress fiber
function renderRootSync(root,lanes) { // Render 阶段入口函数
...
workInProgress = createWorkInProgress(root.current, null);
// workInProgress = root.current.alternate
// 从wip tree的rootFiber开始遍历
do {
try {
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
...
}
function createWorkInProgress(current, pendingProps) {
var workInProgress = current.alternate;
if (workInProgress === null) {
workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
...
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps;
...
}
...
workInProgress.flags = current.flags & StaticMask;
workInProgress.childLanes = current.childLanes;
workInProgress.lanes = current.lanes;
workInProgress.child = current.child;
...
return workInProgress
}
render阶段核心流程:
结合下面代码和上面的黄蓝箭头的图看懂整个遍历过程:
- 整个遍历过程使用了
workInProgress
全局变量,从wip tree的rootFiber节点开始 - 先沿着
fiber.child
往下遍历,过程中执行beginWork(fiber)
- 遇到叶子节点时,先执行
beginWork(fiber)
,然后执行completeWork(fiber)
, 判断是否有sibling
,有的话进入beginWork(siblingFiber)
,没有completeWork(returnFiber)
returnFiber === null
时,第二遍遍历到了rootFiber
,Render阶段结束
// Render 阶段代码
var workInProgress; // workInProgress代表当前已创建的workInProgress fiber
function renderRootSync(root,lanes) { // Render 阶段入口函数
...
workInProgress = createWorkInProgress(root.current, null);
// workInProgress = root.current.alternate
// 从wip tree的rootFiber开始遍历
do {
try {
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
...
}
function workLoopSync() {
// Already timed out, so perform work without checking if we need to yield.
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
// performUnitOfWork方法会创建下一个Fiber节点并赋值给workInProgress,
// 并将workInProgress与已创建的Fiber节点连接起来构成Fiber树。
// 初次调用时,unitOfWork即workInProgress,就是wip rootFiber
var current = unitOfWork.alternate;
// setCurrentFiber(unitOfWork);
var next;
// current: current tree中的Fiber节点,unitOfWork: wip tree中的节点
next = beginWork(current, unitOfWork, subtreeRenderLanes);
if (next === null) {
// 叶子节点
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
function completeUnitOfWork(unitOfWork) {
// 归阶段
var completedWork = unitOfWork;
do {
var current = completedWork.alternate;
var returnFiber = completedWork.return;
var next = void 0;
next = completeWork(current, completedWork, subtreeRenderLanes);
if (returnFiber !== null) {
// Mark the parent fiber as incomplete and clear its subtree flags.
returnFiber.flags |= Incomplete;
returnFiber.subtreeFlags = NoFlags;
returnFiber.deletions = null;
} else {
// We've unwound all the way to the root.
workInProgressRootExitStatus = RootDidNotComplete;
workInProgress = null;
return;
}
var siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
// 有兄弟节点,进入兄弟节点的 performUnitOfWork
workInProgress = siblingFiber;
return;
} // Otherwise, return to the parent
// 无兄弟节点,进入父节点的 completeUnitOfWork
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null) // We've reached the root.
if (workInProgressRootExitStatus === RootInProgress) {
workInProgressRootExitStatus = RootCompleted;
}
}
beginWork
beginWork
的主要工作是传入当前 Fiber节点
, 创建 子Fiber
节点,并把他们连接起来,某些情况下更新 workInProgress.flags
,用来给标记给commit阶段更新fiber。
update
时:如果current
存在,在满足一定条件时可以复用current
节点,这样就能克隆current.child
作为workInProgress.child
,而不需要新建workInProgress.child
。mount
时:除fiberRootNode
以外,current === null
。会根据fiber.tag
不同,创建不同类型的子Fiber节点
- 对于我们常见的组件类型,如(
FunctionComponent
/ClassComponent
/HostComponent
),最终会进入reconcileChildren
方法。reconcileChildren
中:- 对于
mount
的组件,他会创建新的子Fiber节点
- 对于
update
的组件,他会将当前组件与该组件在上次更新时对应的Fiber节点
比较(也就是俗称的Diff
算法),将比较的结果生成新Fiber节点
- 对于
function beginWork(current, unitOfWork, subtreeRenderLanes) {
// update时:如果current存在可能存在优化路径,可以复用current(即上一次更新的Fiber节点)
if (current !== null) {
// ...省略
// 复用current
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderLanes,
);
} else {
didReceiveUpdate = false;
}
// mount时:根据tag不同,创建不同的子Fiber节点
switch (workInProgress.tag) {
case IndeterminateComponent:
// ...
case LazyComponent:
// ...
case FunctionComponent:
// ...
case ClassComponent:
// ...
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);
// ...
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes)
case HostText:
// ...
// ...省略其他类型
}
}
function bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes) {
if (current !== null) {
// Reuse previous dependencies
workInProgress.dependencies = current.dependencies;
}
...
cloneChildFibers(current, workInProgress);
return workInProgress.child;
}
function cloneChildFibers(current, workInProgress) {
if (workInProgress.child === null) {
return;
}
var currentChild = workInProgress.child;
var newChild = createWorkInProgress(currentChild, currentChild.pendingProps);
// createWorkInProgress中,根据current.alternate是否为null决定是否复用current tree Fiber
workInProgress.child = newChild;
newChild.return = workInProgress;
while (currentChild.sibling !== null) {
// 处理wip child fiber的sibling节点
currentChild = currentChild.sibling;
newChild = newChild.sibling = createWorkInProgress(currentChild, currentChild.pendingProps);
newChild.return = workInProgress;
}
newChild.sibling = null;
}
function updateHostRoot(current, workInProgress, renderLanes) {
if (current === null) {
throw new Error('Should have a current fiber. This is a bug in React.');
}
var nextState = workInProgress.memoizedState;
var nextChildren = nextState.element;
var child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
workInProgress.child = child;
// ...
return workInProgress.child;
}
function updateHostComponent(current, workInProgress, renderLanes) {
pushHostContext(workInProgress);
if (current === null) {
tryToClaimNextHydratableInstance(workInProgress);
}
var type = workInProgress.type;
var nextProps = workInProgress.pendingProps;
var prevProps = current !== null ? current.memoizedProps : null;
var nextChildren = nextProps.children;
var isDirectTextChild = shouldSetTextContent(type, nextProps);
if (isDirectTextChild) {
// We special case a direct text child of a host node. This is a common
// case. We won't handle it as a reified child. We will instead handle
// this in the host environment that also has access to this prop. That
// avoids allocating another HostText fiber and traversing it.
nextChildren = null;
} else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
// If we're switching from a direct text child to a normal child, or to
// empty, we need to schedule the text content to be reset.
workInProgress.flags |= ContentReset;
}
markRef(current, workInProgress);
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
if (current === null) {
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
} else {
workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
}
}
completeWork
completeWork
也是针对不同fiber.tag
调用不同的处理逻辑,注意:completeWork
阶段是从下到上,从叶子节点遍历到 rootFiber
;某些情况下更新 workInProgress.flags
,用来给commit阶段更新fiber。
以 HostComponent
(原生dom对应的Fiber类型)为例:
mount时:
Fiber
节点没有alternate,也没有旧的DOM,需要Fiber
节点生成对应的DOM
;并且将子孙DOM
节点插入该Fiber
节点的DOM
中,当到达rootFiber
时,整个dom tree
已经离屏构建完成。update时: 当
update
时,Fiber节点
已经存在对应DOM节点
,所以不需要生成DOM节点
。需要做的主要是处理props
,比如:onClick
、onChange
等回调函数的注册- 处理
style prop
- 处理
DANGEROUSLY_SET_INNER_HTML prop
- 处理
children prop
处理完
props
后,需要更新的props
会放在workInProgress.updateQueue
中,并在commit
阶段渲染在页面上。workInProgress.updateQueue
为数组,偶数索引的值为变化的prop key
,奇数索引的值为对应变化的prop value
。
function completeWork(current, workInProgress, renderLanes) {
switch (workInProgress.tag) {
case IndeterminateComponent:
case xxx:
case HostComponent:
{
popHostContext(workInProgress);
var rootContainerInstance = getRootHostContainer();
var type = workInProgress.type; // dom元素类型
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent$1(current, workInProgress, type, newProps, rootContainerInstance);
if (current.ref !== workInProgress.ref) {
markRef$1(workInProgress);
}
} else {
// mount时
var currentHostContext = getHostContext();
var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
// 为fiber创建对应DOM节点
appendAllChildren(instance, workInProgress, false, false);
// 将子孙DOM节点插入刚生成的DOM节点中。
// 当completeWork处理到rootFiber时,我们已经有一个构建好的离屏DOM树
workInProgress.stateNode = instance;
// DOM节点赋值给fiber.stateNode
if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {
markUpdate(workInProgress);
}
if (workInProgress.ref !== null) {
// If there is a ref on a host node we need to schedule a callback
markRef$1(workInProgress);
}
}
return null;
}
}
appendAllChildren = function (parent, workInProgress, needsVisibilityToggle, isHidden) {
var node = workInProgress.child;
while (node !== null) {
// 遍历所有的child,并将child dom插入该节点的dom中
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode); // node.stateNode存的就是dom
} else if (node.tag === HostPortal) ; else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
};
function appendInitialChild(parentInstance, child) {
parentInstance.appendChild(child);
}
updateHostComponent$1 = function (current, workInProgress, type, newProps, rootContainerInstance) {
var oldProps = current.memoizedProps;
if (oldProps === newProps) {
return;
}
var instance = workInProgress.stateNode;
var currentHostContext = getHostContext();
var updatePayload = prepareUpdate(instance, type, oldProps, newProps, rootContainerInstance, currentHostContext); // TODO: Type this specific to this type of component.
workInProgress.updateQueue = updatePayload;
if (updatePayload) {
markUpdate(workInProgress);
}
};
function markUpdate(workInProgress) {
// Tag the fiber with an update effect. This turns a Placement into
// a PlacementAndUpdate.
workInProgress.flags |= Update;
}
render
阶段除了构造wip tree
,还有一项重要的工作是标记fiber.flags
和fiber.subtreeFlags
,这两项用来标记对Fiber
节点和Fiber
子树的在commit
阶段的处理,如dom的插入,删除,callback的执行等,具体更新 flags 的逻辑在上述代码中已有体现。
commit阶段
Renderer
(渲染器)工作的阶段被称为commit
阶段。commit
阶段可以分为三个主要的子阶段以及一点commit开始的前置工作:
- 在 before mutation阶段之前的准备工作,更新root状态,重置全局变量等
- before mutation阶段(执行
DOM
操作前) - mutation阶段(执行
DOM
操作) - layout阶段(执行
DOM
操作后)
function commitRoot(root, recoverableErrors, transitions) {
// TODO: This no longer makes any sense. We already wrap the mutation and
// layout phases. Should be able to remove.
var previousUpdateLanePriority = getCurrentUpdatePriority();
var prevTransition = ReactCurrentBatchConfig$3.transition;
try {
ReactCurrentBatchConfig$3.transition = null;
setCurrentUpdatePriority(DiscreteEventPriority);
commitRootImpl(root, recoverableErrors, transitions, previousUpdateLanePriority);
} finally {
ReactCurrentBatchConfig$3.transition = prevTransition;
setCurrentUpdatePriority(previousUpdateLanePriority);
}
return null;
}
var nextEffect = null;
function commitRootImpl(root){
var finishedWork = root.finishedWork; // 从finishedWork Fiber(wip.rootFiber)节点开始遍历
...
// commit阶段的准备工作,更新root状态,重置全局变量等
root.finishedWork = null;
root.finishedLanes = NoLanes;
if (root === workInProgressRoot) {
// We can reset these now that they are finished.
workInProgressRoot = null;
workInProgress = null;
workInProgressRootRenderLanes = NoLanes;
}
...
// BeforeMutation 阶段
commitBeforeMutationEffects(root, finishedWork);
// Mutation 阶段
commitMutationEffects(root, finishedWork, lanes);
// 切换current fiber tree
root.current = finishedWork;
// 我们知道componentWillUnmount会在mutation阶段执行。
// 此时current Fiber树还指向前一次更新的Fiber树,在生命周期钩子内获取的DOM还是更新前的。
// componentDidMount和componentDidUpdate会在layout阶段执行。
// 此时current Fiber树已经指向更新后的Fiber树,在生命周期钩子内获取的DOM就是更新后的。
// Layout 阶段
commitLayoutEffects(finishedWork, root, lanes);
}
Before Mutation 阶段
和
render
阶段采用相同的fiber节点深度优先遍历。区别是此处不是遍历到子节点,而是子树都没有effect的Fiber节点(subtreeFlags === Noflags
)。 遍历到这种节点后,对该fiber节点调用commitBeforeMutationEffectsOnFiber(fiber)
,然后有sibling节点就遍历兄弟节点,无sibling遍历父节点。subtreeFlags
是当前 fiber 的子树的标识汇总,目的是防止无意义的完整深度遍历,能够更早地结束遍历。如果直接用 flags,是要遍历到叶子节点才能知道到底谁是要找的最后一个节点。真正对节点处理的函数是:
commitBeforeMutationEffectsOnFiber(fiber)
。
对于ClassComponent
类型节点, 调用了instance.getSnapshotBeforeUpdate
生命周期函数。
对于HostRoot
类型节点, 调用clearContainer
清空了容器节点的子节点(即div#root
这个 dom 节点在commit之前的其他child).
var nextEffect = null;
function commitBeforeMutationEffects(root, firstChild) {
nextEffect = firstChild;
commitBeforeMutationEffects_begin();
}
// 注意此处的遍历顺序,依然是深度优先遍历:
// fiber节点深度优先遍历 -> 子树都没有effect的Fiber节点,commitBeforeMutationEffectsOnFiber(fiber) ->
// 有sibling节点就遍历兄弟节点,无sibling遍历父节点
function commitBeforeMutationEffects_begin() {
while (nextEffect !== null) {
var fiber = nextEffect; // This phase is only used for beforeActiveInstanceBlur.
var child = fiber.child;
if ((fiber.subtreeFlags & BeforeMutationMask) !== NoFlags && child !== null) {
child.return = fiber;
nextEffect = child;
} else {
commitBeforeMutationEffects_complete();
}
}
}
// subtreeFlags 是当前 fiber 的子树的标识汇总,目的是防止无意义的完整深度遍历,能够更早地结束遍历。
// 如果直接用 flags,是要遍历到叶子节点才能知道到底谁是要找的最有一个节点
function commitBeforeMutationEffects_complete() {
while (nextEffect !== null) {
var fiber = nextEffect;
setCurrentFiber(fiber);
try {
commitBeforeMutationEffectsOnFiber(fiber);
} catch (error) {
captureCommitPhaseError(fiber, fiber.return, error);
}
resetCurrentFiber();
var sibling = fiber.sibling;
if (sibling !== null) {
sibling.return = fiber.return;
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
// 处理完节点,判断是否有兄弟节点,若有,处理兄弟节点,若无,返回处理父节点
function commitBeforeMutationEffectsOnFiber(finishedWork) {
// 几乎没做什么工作,主要对ClassCompnent做了一些工作
var current = finishedWork.alternate;
var flags = finishedWork.flags;
if ((flags & Snapshot) !== NoFlags) {
setCurrentFiber(finishedWork);
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
{
break;
}
case ClassComponent:
{
if (current !== null) {
var prevProps = current.memoizedProps;
var prevState = current.memoizedState;
var instance = finishedWork.stateNode; // We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
var snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type ?
prevProps : resolveDefaultProps(finishedWork.type, prevProps), prevState
);
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}
break;
}
case HostRoot:
{
{
var root = finishedWork.stateNode;
clearContainer(root.containerInfo);
}
break;
}
case HostComponent:
case HostText:
case HostPortal:
case IncompleteClassComponent:
// Nothing to do for these component types
break;
default:
{
throw new Error('This unit of ... ' + 'likely caused by a bug in React. ...');
}
}
resetCurrentFiber();
}
}
Mutation 阶段
- 真正操作页面真实
dom
的阶段 - 根据effectTag(
finishedWork_.flags
)执行对应的dom操作 - 调用
safelyDetachRef
解绑ref
function commitMutationEffects(root, finishedWork, committedLanes) {
inProgressLanes = committedLanes;
inProgressRoot = root;
setCurrentFiber(finishedWork);
commitMutationEffectsOnFiber(finishedWork, root);
setCurrentFiber(finishedWork);
inProgressLanes = null;
inProgressRoot = null;
}
function commitMutationEffectsOnFiber(finishedWork, root, lanes) {
var current = finishedWork.alternate;
var flags = finishedWork.flags;
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent:
{
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
return;
}
case ClassComponent:
{
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
if (flags & Ref) {
if (current !== null) {
// 解绑 ref
safelyDetachRef(current, current.return);
}
}
return;
}
}
}
// 读取fiber的 deletions 数组,对这些要删除的 fiber 进行操作
function recursivelyTraverseMutationEffects(root, parentFiber, lanes) {
// Deletions effects can be scheduled on any fiber type. They need to happen
// before the children effects hae fired.
var deletions = parentFiber.deletions;
if (deletions !== null) {
for (var i = 0; i < deletions.length; i++) {
var childToDelete = deletions[i];
try {
commitDeletionEffects(root, parentFiber, childToDelete);
} catch (error) {
captureCommitPhaseError(childToDelete, parentFiber, error);
}
}
}
var prevDebugFiber = getCurrentFiber();
if (parentFiber.subtreeFlags & MutationMask) {
var child = parentFiber.child;
while (child !== null) {
setCurrentFiber(child);
commitMutationEffectsOnFiber(child, root);
child = child.sibling;
}
}
setCurrentFiber(prevDebugFiber);
}
function commitReconciliationEffects(finishedWork) {
// 插入DOM逻辑
// Placement effects (insertions, reorders) can be scheduled on any fiber
// type. They needs to happen after the children effects have fired, but
// before the effects on this fiber have fired.
var flags = finishedWork.flags;
if (flags & Placement) {
try {
commitPlacement(finishedWork);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
} // Clear the "placement" from effect tag so that we know that this is
// inserted, before any life-cycles like componentDidMount gets called.
// TODO: findDOMNode doesn't rely on this any more but isMounted does
// and isMounted is deprecated anyway so we should be able to kill this.
finishedWork.flags &= ~Placement;
}
if (flags & Hydrating) {
finishedWork.flags &= ~Hydrating;
}
}
function commitPlacement(finishedWork) {
var parentFiber = getHostParentFiber(finishedWork); // Note: these two variables *must* always be updated together.
switch (parentFiber.tag) {
case HostComponent:
{
var parent = parentFiber.stateNode;
if (parentFiber.flags & ContentReset) {
// Reset the text content of the parent before doing any insertions
resetTextContent(parent); // Clear ContentReset from the effect tag
parentFiber.flags &= ~ContentReset;
}
var before = getHostSibling(finishedWork); // We only have the top Fiber that was inserted but we need to recurse down its
// children to find all the terminal nodes.
insertOrAppendPlacementNode(finishedWork, before, parent);
break;
}
case HostRoot:
case HostPortal:
{
var _parent = parentFiber.stateNode.containerInfo;
var _before = getHostSibling(finishedWork);
insertOrAppendPlacementNodeIntoContainer(finishedWork, _before, _parent);
break;
}
// eslint-disable-next-line-no-fallthrough
default:
throw new Error('Invalid host parent fiber. This error is likely caused by a bug ' + 'in React. Please file an issue.');
}
}
function insertOrAppendPlacementNode(node, before, parent) {
var tag = node.tag;
var isHost = tag === HostComponent || tag === HostText;
if (isHost) {
var stateNode = node.stateNode;
if (before) {
insertBefore(parent, stateNode, before);
} else {
appendChild(parent, stateNode);
}
} else if (tag === HostPortal) ; else {
var child = node.child;
if (child !== null) {
insertOrAppendPlacementNode(child, before, parent);
var sibling = child.sibling;
while (sibling !== null) {
insertOrAppendPlacementNode(sibling, before, parent);
sibling = sibling.sibling;
}
}
}
}
function insertBefore(parentInstance, child, beforeChild) {
parentInstance.insertBefore(child, beforeChild);
}
function appendChild(parentInstance, child) {
parentInstance.appendChild(child);
}
Layout 阶段
- 该阶段的代码都是在
DOM
修改(mutation阶段
)完成后执行的。该阶段触发的生命周期钩子和hook
可以直接访问到已经改变后的DOM
- 遍历逻辑同 Before Mutation 阶段
- 调用 commitLayoutEffectOnFiber执行相关生命周期函数或者hook相关callback
- 执行commitAttachRef为ref赋值
function commitLayoutEffects(finishedWork, root, committedLanes) {
inProgressLanes = committedLanes;
inProgressRoot = root;
nextEffect = finishedWork;
commitLayoutEffects_begin(finishedWork, root, committedLanes);
inProgressLanes = null;
inProgressRoot = null;
}
function commitLayoutEffects_begin(subtreeRoot, root, committedLanes) {
// Suspense layout effects semantics don't change for legacy roots.
var isModernRoot = (subtreeRoot.mode & ConcurrentMode) !== NoMode;
while (nextEffect !== null) {
var fiber = nextEffect;
var firstChild = fiber.child;
if ( fiber.tag === OffscreenComponent && isModernRoot) {
// 离屏组件逻辑
// xxxx
}
if ((fiber.subtreeFlags & LayoutMask) !== NoFlags && firstChild !== null) {
firstChild.return = fiber;
nextEffect = firstChild;
} else {
commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes);
}
}
}
function commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes) {
while (nextEffect !== null) {
var fiber = nextEffect;
if ((fiber.flags & LayoutMask) !== NoFlags) {
var current = fiber.alternate;
setCurrentFiber(fiber);
try {
commitLayoutEffectOnFiber(root, current, fiber, committedLanes);
} catch (error) {
captureCommitPhaseError(fiber, fiber.return, error);
}
resetCurrentFiber();
}
if (fiber === subtreeRoot) {
nextEffect = null;
return;
}
var sibling = fiber.sibling;
if (sibling !== null) {
sibling.return = fiber.return;
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
function commitLayoutEffectOnFiber(finishedRoot, current, finishedWork, committedLanes) {
if ((finishedWork.flags & LayoutMask) !== NoFlags) {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
{
commitHookEffectListMount(Layout | HasEffect, finishedWork);
break;
}
case ClassComponent:
{
var instance = finishedWork.stateNode;
if (current === null) {
// 调用生命周期 componentDidMount
instance.componentDidMount();
} else {
// 调用生命周期 componentDidUpdate
instance.componentDidUpdate(prevProps, prevState, instance.__reactInternalSnapshotBeforeUpdate);
}
}
case xxxx:
}
}
if ( !offscreenSubtreeWasHidden) {
{
if (finishedWork.flags & Ref) {
commitAttachRef(finishedWork);
}
}
}
}
function commitAttachRef(finishedWork: Fiber) {
// 获取DOM实例和ref建立连接
const ref = finishedWork.ref;
if (ref !== null) {
const instance = finishedWork.stateNode;
// 获取DOM实例
let instanceToUse;
switch (finishedWork.tag) {
case HostComponent:
instanceToUse = getPublicInstance(instance);
break;
default:
instanceToUse = instance;
}
if (typeof ref === "function") {
// 如果ref是函数形式,调用回调函数
ref(instanceToUse);
} else {
// 如果ref是ref实例形式,赋值ref.current
ref.current = instanceToUse;
}
}
}
触发更新(从Source Fiber节点到root)
从触发更新的Fiber节点,沿着parent(fiber.return)遍历到root节点,重新开启 render-commit 流程。
function markUpdateLaneFromFiberToRoot(sourceFiber, lane) {
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
var alternate = sourceFiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, lane);
}
{
if (alternate === null && (sourceFiber.flags & (Placement | Hydrating)) !== NoFlags) {
warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
}
} // Walk the parent path to the root and update the child lanes.
var node = sourceFiber;
var parent = sourceFiber.return;
while (parent !== null) {
parent.childLanes = mergeLanes(parent.childLanes, lane);
alternate = parent.alternate;
if (alternate !== null) {
alternate.childLanes = mergeLanes(alternate.childLanes, lane);
} else {
{
if ((parent.flags & (Placement | Hydrating)) !== NoFlags) {
warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
}
}
}
node = parent;
parent = parent.return;
}
if (node.tag === HostRoot) {
var root = node.stateNode;
return root;
} else {
return null;
}
}
总结
- React是一个js的
Run time
框架,页面在运行时,react逻辑和业务逻辑运行在同一个线程。 - Fiber架构贯穿整个React,在很多阶段都利用到了fiber结构进行遍历,处理每个组件或者dom。
- React运行时有两颗由
Fiber
节点组成的树——wip treee
和current tree
。除了首次加载时只有current tree
,其余时间这两个树都一直存在。 - 在页面初次打开时,需要经过react自身的代码逻辑执行一些阶段后,才会展示dom,首屏的组件不应该太多。
- 结合react源码,我们写代码时,应该注意一些优化点,减少不必要的性能开销。
最后,附上一张render-commit
两阶段的概括图:
