点击这里进入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
的原理。