点击这里进入react原理专栏
ReactDOM.render这篇文章中提到过react更新流程中的render阶段。render阶段这个名字,很容易让人们认为这是react会在这个阶段渲染页面,其实不然,render阶段是react构建workInProgress树的阶段。这篇文章会整体介绍一下render所做的工作。
这篇文章中提到了无论是初次渲染还是后续的页面更新,都会调用performSyncWorkOnRoot。这个方法会调用renderRootSync,也就是render阶段的入口
function renderRootSync(root, lanes) {
var prevExecutionContext = executionContext;
// 设置render阶段的标志
executionContext |= RenderContext;
var prevDispatcher = pushDispatcher();
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
prepareFreshStack(root, lanes);
startWorkOnPendingInteractions(root, lanes);
}
do {
try {
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
// 。。。
return workInProgressRootExitStatus;
}
renderRootSync中有两个比较重要的部分:prepareFreshStack和workLoopSync。首先看一下prepareFreshStack
function prepareFreshStack(root, lanes) {
// ...
// root就是fiberRoot
workInProgress = createWorkInProgress(root.current, null);
// ...
}
这里修改了全局变量workInProgress,而createWorkInProgress会根据workInProgress是否存在,初始化一个workInProgress树的rootFiber。
function createWorkInProgress(current, pendingProps) {
var workInProgress = current.alternate;
if (workInProgress === null) {
// 初次渲染,新建一个fiber对象并初始化
} else {
// 后续更新,重置workInProgress
}
}
现在,准备工作结束后,进入workLoopSync。workLoopSync很简单,就是一个循环
function workLoopSync() {
while (workInProgress !== null) {
// 全局变量workInProgress
performUnitOfWork(workInProgress);
}
}
performUnitOfWork就是对单个fiber节点的处理,该方法会调用beginWork和completeUnitOfWork来完成对一个fiber节点的处理。接下来先说一下fiber树的遍历流程。
render阶段采用深度优先遍历的方式,初次遍历到该节点时调用beginWork处理,后续返回该节点时调用completeUnitOfWork处理,而全局变量workInProgress就指向被访问到的节点。
注意:全局变量
workInProgress是一个指针,会随着fiber树的遍历而变化,并不是workInProgress树的根节点
执行顺序如下:
rootFiber beginWork
App beginWork
div1 beginWork
span1 beginWork
span1 completeUnitOfWork
div2 beginWork
span2 beginWork
span2 completeUnitOfWork
div2 completeUnitOfWork
div1 completeUnitOfWork
App completeUnitOfWork
rootFiber completeUnitOfWork
知道了render阶段的遍历流程,下面看一下beginWork和completeUnitOfWork都做了哪些工作
beginWork
首先,beginWork会根据当前fiber的优先级是否足够,来决定是否复用已有的结构,具体逻辑在这里
if (current !== null) {
// 当前fiber已经挂载过了,现在是后续更新阶段
var oldProps = current.memoizedProps;
var newProps = workInProgress.pendingProps;
if (oldProps !== newProps || hasContextChanged() || (
workInProgress.type !== current.type )) {
didReceiveUpdate = true;
} else if (!includesSomeLane(renderLanes, updateLanes)) {
didReceiveUpdate = false;
switch (workInProgress.tag) {
// 针对不同类型组件的处理
}
// 复用已有结构
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
} else {
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
didReceiveUpdate = true;
} else {
didReceiveUpdate = false;
}
}
} else {
didReceiveUpdate = false;
}
而重点则是后面的switch语句
switch (workInProgress.tag) {
case IndeterminateComponent:
// ...
case LazyComponent:
// ...
case FunctionComponent:
// ...
case ClassComponent:
// ...
}
当不能直接复用已有结构时,会根据不同组件fiber类型,进入不同的更新逻辑。之后的文章会主要介绍类组件和函数组件时如何创建的更新的。现在先介绍一下beginWork会完成哪些工作:对于类组件与函数组件而言,beginWork会计算更新之后的状态,并执行reconcileChildren方法,也就是我们常说的diff阶段。在diff过程中,会根据current树来创建对应的workInProgress树节点,并根据更新前后的渲染结果,为fiber打上effect,来说明该fiber是更新,新增还是删除,最终,beginWork会返回workInProgress.child,也就是当前处理的fiber节点的第一个子节点。
completeUnitOfWork
当workInProgress指针来到fiber树的叶子节点时,就会执行completeUnitOfWork方法,这个方法主要会根据fiber节点的创建对应的dom节点,并将其插入到自己的父节点中,并且收集fiber的effect,创建一个effectList链表,为commit阶段使用。
这篇文章介绍了render阶段的整体流程,后续会为大家介绍类组件和函数组件时如何挂载和更新的,也就是setState和hooks的原理。