React中的render流程
一、前沿
我们都知道 react 整体分为三个阶段 Scheduler、render、commit。Scheduler负责任务的调度;render负责生成fiber树,打上副作用标签,diff等等。commit负责生成真实的dom并渲染到浏览器上。在之前的文章中我们已经介绍过了 scheduler。那么在本章中我们介绍一下 render流程
二、整体流程
我们的 render主要分为两大部分。beginWork、complete。
beiginWork
首先我们的 react会从 rootFiber开始按照深度优先递归的方式,遍历 Fiber节点。并且会对每个 Fiber都执行一次 beiginwork方法。该方法主要负责根据已有的Fiber节点,创建出新的子Fiber节点。()
- Fiber -child-> childFiber
- childFiber -return-> Fiber
然后两者通过child和return属性连接起来
当执行到叶子 Fiber节点时就会执行向上“归”的流程也就是 complete
complete
在 complete中会根据不同的 tag类型执行不同的逻辑
function completeWork (current, workInProgress, renderLanes) {
switch (workInProgress.tag) {
case IndeterminateComponent:
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:
bubbleProperties(workInProgress);
return null;
case ClassComponent:
{
var Component = workInProgress.type;
if (isContextProvider(Component)) {
popContext(workInProgress);
}
bubbleProperties(workInProgress);
return null;
}
......
当执行完一个 Fiber节点的 complete阶段后,Fiber会先寻找 silbing(兄弟节点)。如果有兄弟节点就进入兄弟节点的 complete流程。如果没有兄弟节点,那么就进入 return (父节点)的 complete流程。一直到根节点
至此整个 render流程结束
例子
我们先来看一段代码实例
function App() {
return <div>
huyunkun
<span>test</span>
</div>
}
根据这个 App组件我们最终会生成一棵这样子的 Fiber树

首先让我们来从源码的角度看一看在 mount时,是如何生成这颗 Fiber树的。
mount时的beginWork和compele
render流程的入口为 performConcurrentWorkOnRoot或者 performSyncWorkOnRoot
最终 performSyncWorkOnRoot会进入到 workLoopSync。而 performConcurrentWorkOnRoot会进入到 workLoopConcurrent
function workLoopSync () {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function workLoopConcurrent () {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
在 workLoopConcurrent会通过 shouldYield来判断浏览器是否还有空余时间,从而实现时间切片。最终他们都会进入 performUbitOfWork方法。(想要了解更多时间切片的内容可以看笔者的另一片文章 scheduler如何时间时间切片)。
function performUnitOfWork (unitOfWork) {
var next;
// beginwork 阶段
if ((unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
next = beginWork$1(current, unitOfWork, renderLanes$1);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
next = beginWork$1(current, unitOfWork, renderLanes$1);
}
// 已经执行到叶子结点的时候就开始执行complete流程
// complete 阶段
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
// 将当前执行的节点 执行 next
workInProgress = next;
}
ReactCurrentOwner$2.current = null;
}
我们可以看到该方法的主要功能就是执行 Fiber节点的 beiginWork和complete。
mount时的beiginWork
下面我们正式进入 beiginWork方法的分析
function beginWork (current, workInProgress, renderLanes) {
if (current !== null) {
// 这里一大段逻辑判断是否可以复用子Fiber
} else {
didReceiveUpdate = false;
}
workInProgress.lanes = NoLanes;
switch(workInProgress.tag) {
case IndeterMinateComponent:
{
return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);
}
}
}
我们可以看到,
- 在
beiginWork中通过current !== null判断当前是否为mount。如果current !== null说明当前为update,否则为mount。 - 接下来进入到mount的判断中,
didReceiveUpdate被赋值为false。使用didReceiveUpdate来判断是否复用子Fiber节点。 - 接着赋值优先级
lanes为Nolanes。 - 最后进入到不同组件类型的执行逻辑中。因为当前是
mount,react还不知道当前组件是Function还是Class因此会进入IndeterMinateComponent类型的逻辑。
function mountIndeterminateComponent (_current, workInProgress, Component, renderLanes) {
......
reconcileChildren(null, workInProgress, value, renderLanes);
return workInProgress.child;
}
function reconcileChildren (current, workInProgress, nextChildren, renderLanes) {
if (current === null) {
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
} else {
// 子fiber节点
// current.child是fiber nextChildren是reactElement对象
workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
}
}
最终进入到 reconcileChildren方法中。通过判断 current是否为null进入到不同的逻辑。当前我们是mount状态,current为 null。因此我们会进入 mountChildFibers。接下去生成新的子 Fiber。这部分和 diff算法有关。笔者会另起一篇文章来介绍diff算法。diff过程中会给每一个 Fiber打上不同的flags,表示要对该 Fiber做不同的操作。那么到这里针对一个 Fiber节点在mount状态下的 beginWork阶段已经结束。当执行到叶子节点的时候,我们便会开启 complete流程。
最后我们来看一下 flags
export const Placement = /* */ 0b00000000000000000000000010;
export const Update = /* */ 0b00000000000000000000000100;
export const ChildDeletion = /* */ 0b00000000000000000000001000;
export const ContentReset = /* */ 0b00000000000000000000010000;
export const Callback = /* */ 0b00000000000000000000100000;
export const DidCapture = /* */ 0b00000000000000000001000000;
export const ForceClientRender = /* */ 0b00000000000000000010000000;
export const Ref = /* */ 0b00000000000000000100000000;
export const Snapshot = /* */ 0b00000000000000001000000000;
export const Passive = /* */ 0b00000000000000010000000000;
export const Hydrating = /* */ 0b00000000000000100000000000;
export const Visibility = /* */ 0b00000000000001000000000000;
export const StoreConsistency = /* */ 0b00000000000010000000000000;
mount时的complete
function completeUnitOfWork (unitOfWork) {
var completedWork = unitOfWork
var next
do {
......
next = completeWork(completedWork)
var siblingFiber = completedWork.sibling
if (siblingFiber !== null) {
workInProgress = siblingFiber
return
}
completedWork = returnFiber
workInProgress = completedWork
} while(completedWork !== null)
}
- 在mount时先执行
completeWork函数。 - 接着判断当前的
fiber节点是否有兄弟节点,有兄弟节点则执行兄弟节点的completeWork流程,没有则会执行父节点的completeWork流程。
function completeWork (current, workInProgress, renderLanes) {
var newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case HostComponent:
{
// mount阶段
var _rootContainerInstance = getRootHostContainer();
// 生成dom节点
var instance = createInstance(_type, newProps, _rootContainerInstance, _currentHostContext, workInProgress);
// 将子孙DOM节点插入刚生成的DOM节点中
appendAllChildren(instance, workInProgress, false, false);
// DOM节点赋值给fiber.stateNode
// 处理props
if (finalizeInitialChildren(instance, _type, newProps)) {
markUpdate(workInProgress);
}
}
}
}
我们关注生成真实dom节点的 HostCOmponent类型的节点。在 complete会生成真实的dom节点 instance。然后通过 appendAllChildren方法将真实的dom节点插入到父节点当中。当 complete到根节点的时候我们就可以得到一棵完整的dom树 最后处理一遍 props就完成了 mount 时 complete的流程。
update中的beginWork
在beginWork中 mount和update的逻辑大体相同。主要区别在于在update中加上了优化逻辑。(通过标记didReceiveUpdate来判断子fiber是否可复用)
function beginWork (current, workInProgress, renderLanes) {
// 是否要协调的逻辑
// current !== null 则是update时触发
if (current !== null) {
//update阶段执行
// 如果current存在,在满足一定条件时可以复用current节点,这样就能克隆current.child作为workInProgress.child,而不需要新建workInProgress.child
var oldProps = current.memoizedProps;
var newProps = workInProgress.pendingProps;
if (oldProps !== newProps || hasContextChanged() || ( //
workInProgress.type !== current.type)) {
// 标记是否已经完成更新
didReceiveUpdate = true;
} else {
var hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(current, renderLanes);
// 如果都不是的情况
if (!hasScheduledUpdateOrContext && // If this is the
(workInProgress.flags & DidCapture) === NoFlags) {
didReceiveUpdate = false;
return attemptEarlyBailoutIfNoScheduledUpdate(current, workInProgress, renderLanes);
}
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
didReceiveUpdate = true;
} else {
didReceiveUpdate = false;
}
}
}
}
- 首先依然通过
current !== null来判断当前是否是mount阶段。当前为update阶段。进入update的逻辑 - 然后满足某些情况下
didReceiveUpdate会被标记为false。表示子fiber可复用。以下为可复用的情况
oldProps !== newProps ,workInProgress.type !== current.type!hasScheduledUpdateOrContext && (workInProgress.flags & DidCapture) === NoFlags
接下来就会进入到不同tag的逻辑中了
update中的complete
对于hostComponet类型,在更新阶段已经有dom了所以不需要重新生成dom。只需要处理props。
当进入到complete时,tag为HostComponent类型会进入到updateHostComponent的逻辑中。
在updateHostComponent中处理完props的逻辑后会生成fiber的updateQueue。当所有的complete工作都技术之后会进入到commit阶段。到这里我们的render阶段也就结束了
总结
最后我们总结一下。整个render过程会分为beginWork和complete两个过程(也就是所谓的递和归)。在beginWork中我们会进行diff然后给fiber节点打上flags。在complete中会生成dom节点,并处理props属性。最终交给commit。渲染出真实的dom
转载自:https://juejin.cn/post/7161741764712693797