React源码学习进阶(四)render流程的入口逻辑详解
本文采用React v16.13.1版本源码进行分析
源码解析
接下来我们分析一下updateContainer的逻辑,它的入口在packages/react-reconciler/src/ReactFiberReconciler.js中:
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): ExpirationTime {
const current = container.current;
const currentTime = requestCurrentTimeForUpdate();
const suspenseConfig = requestCurrentSuspenseConfig();
const expirationTime = computeExpirationForFiber(
currentTime,
current,
suspenseConfig,
);
const context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}
const update = createUpdate(expirationTime, suspenseConfig);
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element};
callback = callback === undefined ? null : callback;
if (callback !== null) {
if (__DEV__) {
if (typeof callback !== 'function') {
console.error(
'render(...): Expected the last optional `callback` argument to be a ' +
'function. Instead received: %s.',
callback,
);
}
}
update.callback = callback;
}
enqueueUpdate(current, update);
scheduleWork(current, expirationTime);
return expirationTime;
}这段代码里面有几个非常关键的信息:
通过
computeExpirationForFiber计算出的expirationTime,这个涉及到调度优先级,我们暂时可以认为它就是Sync的常量值。调用
createUpdate创建了一个update,并调用enqueueUpdate将update入队。调用
scheduleWork启动整个更新流程。
接下来先看一下createUpdate的逻辑,源码位于packages/react-reconciler/src/ReactUpdateQueue.js:
可以看到,update其实就是一个环状链表的结构,自己指向自己:

然后通过enqueueUpdate方法将update放入之前fiber的updateQueue里:
实际上此时的update被存储在updateQueue的shared.pending字段中。
scheduleWork启动
scheduleWork的启动代码位于packages/react-reconciler/src/ReactFiberWorkLoop.js中:
这段代码实际上核心关注两个点:
调用
markUpdateTimeFromFiberToRoot获取root,在这个过程中会更新Fiber的expirationTime。调用
performSyncWorkOnRoot,开始执行work的逻辑。
首先看一下markUpdateTimeFromFiberToRoot的逻辑:
这里其实在root挂载时,只更新了当前的rootFiber,以及调用markRootUpdatedAtTime更新了root的firstPendingTime:
接下来就是执行performSyncWorkOnRoot的逻辑了:
这个函数实际上才是真正的挂载更新入口,排除掉其他干扰,核心逻辑有三点:
调用
prepareFreshStack,初始化workInProgress的信息调用
workLoopSync,启动render流程调用
finishSyncRender,启动commit流程
首先我们看一下prepareFreshStack做了什么:
可以看到在这里创建了一个workInProgress,它的创建方法位于packages/react-reconciler/src/ReactFiber.js:
实际上创建workInProgress的过程可以简单理解为将原来的fiber拷贝了一份。
接着我们来看看重头戏workLoopSync启动render流程:
这里是我们接触到的第一层循环,实际上这里的workInProgress会被不断改变,赋值为performUnitOfWork的返回值,而performUnitOfWork每次则会返回子节点或兄弟节点,从而开启整个Fiber树的遍历迭代流程。
最后我们再来看看performUnitOfWork的逻辑:
这段逻辑的核心在于两个点:
调用
beginWork处理节点,拿到下一个节点如果下一个节点不存在,则调用
completeUnitOfWork处理节点,并拿到下一个节点
请记住这里的beginWork和completeUnitOfWork的调用逻辑,实际上它们在遍历过程中扮演着进入节点和离开节点所做的工作,后面我们将围绕这两个核心Fiber操作展开详细的讲解。
小结一下
整体在进入正式的render流程之前,React Fiber本身其实做了大量工作,从入口处更能理清整个挂载更新过程的主体脉络:

在有了这个脉络之后,我们就可以把重点放在三个方向上:
beginWork具体做了什么事情?(实际上它很重要,整个reconciler都是这个阶段完成)completeWork具体做了什么事情?commit具体做了什么事情?
搞懂了这三个方面,我们就知道在Fiber架构下,React是如何进行渲染更新的了。
Last updated