- React源码入门篇(一)第一篇文章我们主要初步对React源码进行划分, 然后从
React.createRoot()和ReactDom.reader中的render和updateContainer函数入手
- React源码阅读(二)- Scheduler 文章二我们从
scheduleUpdateOnFiber入手介绍了解了React的Scheduler
- Scheduler调用了一系列函数,最后又调用了
renderRootSync或者renderRootConcurrent走入了我们的第三部分render, 那么这篇文章就从renderRootSync开始
一、renderRootSync函数
我们直接看关键逻辑,在起始的时候workInProgressRoot和workInProgressRootRenderLanes都为null, 故会调用prepareFreshStack。 接着通过一个while循环, 调用了workLoopSync()函数, 当workLoopSync()流程走完后, 重置workInProgress进入commit标识render阶段结束
js
function renderRootSync(root: FiberRoot, lanes: Lanes) {
const prevExecutionContext = executionContext;
executionContext |= RenderContext;
const prevDispatcher = pushDispatcher();
....
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
...
prepareFreshStack(root, lanes);
}
// 进入了一个循环, 主要是出现问题的时候会重试吧
do {
try {
workLoopSync();
break; // 实际上正常执行完一次就会被break出去
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
resetContextDependencies(); // 重置上下文信息
executionContext = prevExecutionContext;
popDispatcher(prevDispatcher);
...
// 置空标识当前render阶段结束, 没有正在执行的render过程
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
return workInProgressRootExitStatus;
}
prepareFreshStack
- 在初始化步骤去调用
prepareFreshStack的作用就是给各种变量做初始化。 其中很重要的包括给workInprogress赋值
ts
function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
...
workInProgressRoot = root;
// 根据hostRootFiber创建rootWorkInProgress. 赋值给workInPropgress
const rootWorkInProgress = createWorkInProgress(root.current, null);
workInProgress = rootWorkInProgress;
workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
workInProgressRootExitStatus = RootInProgress;
workInProgressRootFatalError = null;
workInProgressRootSkippedLanes = NoLanes;
workInProgressRootInterleavedUpdatedLanes = NoLanes;
workInProgressRootRenderPhaseUpdatedLanes = NoLanes;
workInProgressRootPingedLanes = NoLanes;
workInProgressRootConcurrentErrors = null;
workInProgressRootRecoverableErrors = null;
return rootWorkInProgress;
}
二、 workLoopSync函数
看到xxxxLoop的命名就知道这里又存在了一个循环, workLoopSync函数的逻辑很简单, 当workInPropgress不为空的时候, 循环调用performUnitOfWork
js
function workLoopSync() {
// Perform work without checking if we need to yield between fiber.
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
三、 performUnitOfWork函数
performUnitOfWork其实就是通过beginWork找next节点的过程,unitOfWork就是当前传入的workInppogress, 当next节点为空时则说明完成当前工作单元完成, 此时需要completeUnitOfWork,next节点不为空时, 赋值给workInProgress, 这样就会继续调用performUnitOfWork
js
function performUnitOfWork(unitOfWork: Fiber): void {
// 拿到是这个节点上一次的内容,也就是旧节点
// 当在初次挂载阶段的时候, 只有在处理hostFiberRoot时,current才不为空,处理其他节点时都为空
const current = unitOfWork.alternate;
...
// 通过调用beginWork生成next节点
let next;
// 对mode进行了判断, 无论走哪个逻辑核心都在next = beginWork(...)
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
next = beginWork(current, unitOfWork, renderLanes);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
next = beginWork(current, unitOfWork, renderLanes);
}
....
if (next === null) {
// 没有子节点的话则说明当前工作单元完成
completeUnitOfWork(unitOfWork);
} else {
// 还有子节点的话workInProgress执行子节点, 构成循环链路
workInProgress = next;
}
ReactCurrentOwner.current = null;
}

四、beginWork函数
- 传入的
current指的是当前页面使用的Fiber结构 - 传入的
workInProgress指的是当前正在构造的Fiber结构 - 这其实就是我们经常提到的双缓存,
React的流程就是从根节点开始遍历Fiber树,然后标记发生改变的FiberNode,然后再将改变映射到真实的UI上,那这个过程不应该直接影响到当前显示使用的Fiber, 故就存在了两个fiber, 旧的展示于页面, 然后根据旧Fiber和state的变化生成新的Fiber, 然后再切换。两颗Fiber树之间通过alternate属性链接。 - 一开始第一次进入
beginWork的时候拿到的current和workInProgress都是hostRootFiber,tag为3。
js
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
...
// 对不同tag进行不同的处理
switch (workInProgress.tag) {
.....
}
}
其中通过Switch Case对不同的tag进行了不同的处理, 但是不同的方法内部都会调用 reconcileChildren, React内部定义了25种tag, 比较常见的有FunctionComponent, ClassComponent,HostComponent, hostRoot, ForwardRef, MemoComponent, LazyComponent, 等等 
五、updateHostRoot
我们先从hostFiber入手, 在遇到tag为HostRoot的时候会调用updateHostRoot 
updateHostRoot: 针对于tag为hostRoot的时候会执行的函数, 用于处理HostFiberNode
js
function updateHostRoot(current, workInProgress, renderLanes) {
pushHostRootContext(workInProgress);
const nextProps = workInProgress.pendingProps;
const prevState = workInProgress.memoizedState;
const prevChildren = prevState.element;
// 更新updateQueue
cloneUpdateQueue(current, workInProgress);
// 根据updateQueue更新memozidState---后面再做详细解释
processUpdateQueue(workInProgress, nextProps, null, renderLanes);
// 拿到更新之后的state
const nextState: RootState = workInProgress.memoizedState;
// 文章一的createFiberRoot中我们讲过了, hostFiberNode的stateNode指向的是FiberRootNode
const root: FiberRoot = workInProgress.stateNode;
pushRootTransition(workInProgress, root, renderLanes);
....
const nextChildren = nextState.element;
if (supportsHydration && prevState.isDehydrated) {
....// 服务端渲染相关, 这里直接跳过了
} else {
// 判断是否可以直接复用
if (nextChildren === prevChildren) {
// 判断是否有child更新
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
// 开始reconcile
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
return workInProgress.child;
}
cloneUpdateQueue
- 其中通过
cloneUpdateQueue处理updateQueue。 本质上就是将current Fiber的updateQueue复制一遍给workInProgress Fiber
js
export function cloneUpdateQueue<State>(
current: Fiber,
workInProgress: Fiber,
): void {
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
if (queue === currentQueue) { // 还没处理过的时候引用一致
const clone: UpdateQueue<State> = {
baseState: currentQueue.baseState,
firstBaseUpdate: currentQueue.firstBaseUpdate,
lastBaseUpdate: currentQueue.lastBaseUpdate,
shared: currentQueue.shared,
effects: currentQueue.effects,
};
workInProgress.updateQueue = clone;
}
}
reconcileChildren
- 对于
hostRoot来说,第一次走到beiginWork后会走到reconcileChildren, 这个函数的逻辑很简单,就做了一个判断, 判断current是否为空,空的话调用mountChildFibers,currentFirstChild传的为空,current不为空的话调用reconcileChildFibers进行更新,currentFirstChild的为当前current Fiber的child
js
export function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderLanes: Lanes,
) {
if (current === null) {
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
} else {
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderLanes,
);
}
}
接着我们发现这两个方法其实都是调用同一个方法的返回值, 区别就在于传入的参数shouldTrackSideEffects不同(用于表示是否追踪副作用)

六、 reconcileChildFibers
前面我们也提到了 在首次挂载处理hostRoot的时候,current不为null, 故自然走到了reconcileChildFibers, 在这个函数中又涉及了$$typeof的Switch Case判断, React中也定义了多种类型的$$typeof

js
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
lanes: Lanes,
): Fiber | null {
const isUnkeyedTopLevelFragment =
typeof newChild === 'object' &&
newChild !== null &&
newChild.type === REACT_FRAGMENT_TYPE &&
newChild.key === null;
if (isUnkeyedTopLevelFragment) {
newChild = newChild.props.children;
}
// 单子节点的处理
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: // 最常见的函数组件呀, 类组件呀
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
lanes,
),
);
case REACT_PORTAL_TYPE:
return placeSingleChild(
reconcileSinglePortal(
returnFiber,
currentFirstChild,
newChild,
lanes,
),
);
case REACT_LAZY_TYPE: // lazy组件
const payload = newChild._payload;
const init = newChild._init;
// TODO: This function is supposed to be non-recursive.
return reconcileChildFibers(
returnFiber,
currentFirstChild,
init(payload),
lanes,
);
}
// 数组子节点的处理
if (isArray(newChild)) {
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
lanes,
);
}
if (getIteratorFn(newChild)) {
return reconcileChildrenIterator(
returnFiber,
currentFirstChild,
newChild,
lanes,
);
}
throwOnInvalidObjectType(returnFiber, newChild);
}
// 文本子节点
if (
(typeof newChild === 'string' && newChild !== '') ||
typeof newChild === 'number'
) {
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
'' + newChild,
lanes,
),
);
}
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
- 可以看到无论是走到了哪个分支, 都会调用
placeSingleChild, 传入的参数是根据不同节点情况调用不同reconcile函数的返回值 - 我们从最常见的
react-element的情况深入
js
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
lanes,
),
);
reconcileSingleElement
- 先看
reconcileSingleElement一开始我们拿到的child为null, 故会直接走到createFiberElement, 然后处理它的Ref和return属性, 最后返回一个Fiber结构
createFiberFromElement
- 从语义上也很好理解, 这个函数的用途就是通过
element构造出对应的Fiber结构,调用链路是createFiberFromTypeAndProps->reateFiber->FiberNode, 文章一已经描述了Fiber相关的这里就不赘述了
js
export function createFiberFromElement(
element: ReactElement,
mode: TypeOfMode,
lanes: Lanes,
): Fiber {
let owner = null;
const type = element.type;
const key = element.key;
const pendingProps = element.props;
const fiber = createFiberFromTypeAndProps(
type,
key,
pendingProps,
owner,
mode,
lanes,
);
return fiber;
}
placeSingleChild
- 那我们再看
placeSingleChild, 其中判断了shouldTrackSideEffects, 我们在前面也提到了在进行首次Fiber处理的时候, 只有在处理hostFiberRoot的时候current是不为null的,此时shouldTrackSideEffects为true, 故Fiber会打上Placement的标记, 最后直接整个Fiber挂载上去即可
js
// mount都是插入的操作
function placeSingleChild(newFiber) {
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.flags |= Placement;
}
return newFiber;
}
- 我们对以下的简单代码执行以上流程,然后看看他生成的Fiber树. 可以看到现在就是一条路走到嘿, 通过
child串到底,在return回来- tag: 3 hostRoot
- tag: 8 Mode
- tag: 0 FunctionComponenet
- tag 5 hostComponent


七、 reconcileChildrenArray
- 诶你会发现我们的三剑客之一
sibling还没出现呢, 还记得我们之前在reconcileChildFibers还做了一个isArray(newChild)的判断,那我们浅浅改一下代码, 让他们出现多个节点并行,然后再调试一波, 可以看到当前newChild是一个长度为4的数组


isArray()判断返回为true的时候会调用reconcileChildrenArray(), 这个函数就是用来处理多节点的。其实就是遍历newChild然后挨个生成Fiber, 通过sibling连接起来, 最后返回头结点
js
function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<*>,
lanes: Lanes,
): Fiber | null {
let resultingFirstChild: Fiber | null = null; // 结果返回出去的child
let previousNewFiber: Fiber | null = null; // 上一个处理的child
let oldFiber = currentFirstChild; // 旧的child
let lastPlacedIndex = 0;
let newIdx = 0; // 从0开始
let nextOldFiber = null;
.....
// 说明处于初始化过程呢, 还没有对应的旧Fiber, 此时会快速生成, 因为不需要经过对比,直接都是插入
if (oldFiber === null) {
// 遍历element数组
for (; newIdx < newChildren.length; newIdx++) {
// 根据element生成对应的Fiber结构
const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
if (newFiber === null) { // 无效的Fiber, 跳过
continue;
}
...
if (previousNewFiber === null) { // 初次执行的时候这个就为null
resultingFirstChild = newFiber; // 给他赋值让最后返回的结果是指向头指针的
} else {
// 不为null的时候说明前面已经有生成一些Fiber结构了
// 此时通过sibling将他们链接起来即可
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber; // 移动指针, 让previous总是指向当前处理的前一个Fiber
}
...
return resultingFirstChild;
}
.....
八、completeUnitOfWork
依照我们上面所讲述的逻辑,因为Fiber是通过next去逐行 生成的,故最后生成的fiber树如下所示(包含在step中的节点), 但是我们最终的目标的Fiber结构应该包括虚线框中的节点

- 相关的逻辑还是处于
performUnitOfWork, 当我们处理完step5之后, 再执行beiginWork此时拿到的结果为null, 此时会走向completeUnitOfWork() - 可以看到该函数的核心逻辑在
completeWork, 拿到next节点, 如果next节点存在值, 则赋值给workInprogress, 此时因为workInprogress不为空,故又会继续执行beginWork。 如果next节点为空的话,此时就会找到该节点的sibling节点,然后赋值给workInprogress, 那又开始找beginwork。 如果sibling节点还是为空的话, 则通过return找到其父节点,
js
function completeUnitOfWork(unitOfWork: Fiber): void {
// 当前的节点, 其子节点找完为null
let completedWork = unitOfWork;
// 又开循环
do {
// 旧节点,也就是当前用于页面展示的节点
const current = completedWork.alternate;
// 该节点的父节点
const returnFiber = completedWork.return;
// 通过判断当前节点的flags标签, 当该标签未被处理过的时候flags为Incomplete
if ((completedWork.flags & Incomplete) === NoFlags) {
let next;
if (
!enableProfilerTimer ||
(completedWork.mode & ProfileMode) === NoMode
) {
next = completeWork(current, completedWork, subtreeRenderLanes);
} else {
startProfilerTimer(completedWork);
next = completeWork(current, completedWork, subtreeRenderLanes);
stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
}
if (next !== null) {
workInProgress = next;
return;
}
} else {....}
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber
workInProgress = completedWork;
} while (completedWork !== null);
...
来个图, 会更清晰, 可以看到无论走哪条分支, 最后总会对workInProgress进行重新赋值, 那么就有可能会重新走到beginWork 
先不考虑beginwork和completeUnitofWork具体还做了其他什么, 我们可以拿一个比较复杂的Fiber结构走一遍流程, 大体的一个过程如图所示
- 大体就是
beiginWork就负责一条路走到黑, 啥也不管就直走, 发现,诶, 没路了就交给completeUnitofWork去处理 completeUnitofWork就像拿着电灯, 找找这找找那, 看后看右看前, 哪有路就往哪走, 直到实在没有路了为止

九、completeWork
再来具体看看completeUnitOfWork的核心逻辑completeWork拿到的next节点是什么, 以及他具体做了哪些操作。
- 整个函数有七百多行,整体都是基于Fiber tag的判断然后执行不同的逻辑, 我们抽取部分我们关注的看看就OK
js
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const newProps = workInProgress.pendingProps;
popTreeContext(workInProgress);
// 这里跟beiginWork一致的,都是通过判断Fiber的tag标签去判断执行什么逻辑
switch (workInProgress.tag) {
case IndeterminateComponent:
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent: .....
case ClassComponent: ....
case HostRoot: ...
case HostComponent: ....
case HostText: ...
case SuspenseComponent: ....
case HostPortal: ...
case ContextProvider: ...
case IncompleteClassComponent: ...
case SuspenseListComponent: ....
case OffscreenComponent:
case LegacyHiddenComponent: ....
case CacheComponent: ...
根据我们之前的Fiber结构, 当我们走入completeWork的时候,此时Fiber的tag为5, 那么此时会走到hostComponent, 也是比较核心的逻辑, 我们可以了解一波, 对应逻辑如下 
js
{
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer(); //拿到根节点的DOM
const type = workInProgress.type; // 拿到类型
// 存在旧节点的时候进行更新操作
if (current !== null && workInProgress.stateNode != null) {
...
} else {
...
const currentHostContext = getHostContext(); // 拿到上下文信息
const wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) { ... } else {
// 根据Fiber创建对应的DOM结构信息
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
// 基于appendChild通过找寻Fiber收集DOM
appendAllChildren(instance, workInProgress, false, false);
// 将创建好的DOM结构信息存放到stateNode属性中(这样appendAllChildren才能通过Fiber收集到hhh)
workInProgress.stateNode = instance;
if (
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
currentHostContext,
)
) {
markUpdate(workInProgress);
}
}
if (workInProgress.ref !== null) {
markRef(workInProgress);
}
}
bubbleProperties(workInProgress);
return null; // 处理该类型节点的时候都是直接返回null, 因为没有下一个节点需要去处理
}
- 其中
getRootHostContainer就是拿到当前的rootDOM节点, 本质上是去拿了rootInstanceStackCursor.current的值,我们在上面执行beginwork -> updateHostRoot的时候第一步就调用了pushHostRootContext,其中就是拿到hostRoot的stateNode(其实就是根应用节点)的containerInfo(指向的就是传入的DOM了)。
js
function getRootHostContainer(): Container {
const rootInstance = requiredContext(rootInstanceStackCursor.current);
return rootInstance;
}

createInstance
- 其中的
createInstance的逻辑也比较重要, 可以看到分别调用了三个函数,我们逐个来了解一下
js
export function createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): Instance {
let parentNamespace: string;
if (__DEV__) {... } else {
parentNamespace = ((hostContext: any): HostContextProd);
}
// 创建真实DOM对象
const domElement: Instance = createElement(
type,
props,
rootContainerInstance,
parentNamespace,
);
// 将Fiber挂载在创建好的DOM上
precacheFiberNode(internalInstanceHandle, domElement);
// 将props挂载在创建好的DOM上
updateFiberProps(domElement, props);
return domElement;
}
creatElement, 通过判断节点类型决定采创建对应的DOM对象, 当type为svg或者math的时候, 会修改namespaceURI, 然后调用createElementNS创建节点。其他情况则采用createElement
js
export function createElement(
type: string,
props: Object,
rootContainerElement: Element | Document | DocumentFragment,
parentNamespace: string,
): Element {
let isCustomComponentTag;
// 根据上面拿到的DOM对象拿到对应的document对象
const ownerDocument: Document = getOwnerDocumentFromRootContainer(
rootContainerElement,
);
let domElement: Element;
let namespaceURI = parentNamespace;
if (namespaceURI === HTML_NAMESPACE) {
namespaceURI = getIntrinsicNamespace(type);
}
if (namespaceURI === HTML_NAMESPACE) {
...
domElement = ownerDocument.createElement(type);
} else {
domElement = ownerDocument.createElementNS(namespaceURI, type);
}
return domElement;
}
拿到的document和创建好的DOM如图所示 
- 再看
precacheFiberNode, 就是把对应的Fiber挂载在创建好的DOM节点上
- 再看
updateFiberProps, 将对应的props挂载在创建好的DOM节点上
故createInstance总体的逻辑都是在创建一个我们所需要的DOM节点。
appendAllChildren
紧接着又调用了appendAllChildren, 可以看到该函数的逻辑都是基于child不为空的情况下调用的。 故我们调试该节点进入completeWork 
js
appendAllChildren = function(
parent: Instance, // 创建好的DOM节点
workInProgress: Fiber, // 当前的Fiber
needsVisibilityToggle: boolean, // false
isHidden: boolean, // false
) {
let node = workInProgress.child;
while (node !== null) { // 有child的情况下
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
} 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; // 有兄弟节点就走向兄弟节点
}
};
- 其中在
tag为HostComponent或者HostText(相当于叶子节点)的时候又调用了appendInitialChild, 逻辑如下, 调用了DOM上的appendChild方。
可以说appendAllChildren的作用就是把他所有孩子级的DOM节点通过appendChild收集起来,这个例子最后是拿到了四个Children node
这一步很重要, 可以说把零散的DOM节点串成了一棵DOM树
分别为以下这几个节点对应的DOM结构 
bubbleProperties
继续看bubbleProperties, 看名字就大概他的作用了。将一些属性冒泡上去。 我们再来看具体逻辑。实际上就是收集了子节点的lanes和flags, 然后累加了对应的actualDuration和treeBaseDuration
收集flags的步骤也很重要。 commit阶段很多都需要依赖subtreeFlags去判断
js
function bubbleProperties(completedWork: Fiber) {
const didBailout =
completedWork.alternate !== null &&
completedWork.alternate.child === completedWork.child; // 判断是否发生了回退
let newChildLanes = NoLanes;
let subtreeFlags = NoFlags;
if (!didBailout) {
if ((completedWork.mode & ProfileMode) !== NoMode) {
let actualDuration = completedWork.actualDuration;
let treeBaseDuration = ((completedWork.selfBaseDuration: any): number);
let child = completedWork.child;
while (child !== null) {
newChildLanes = mergeLanes(
newChildLanes,
mergeLanes(child.lanes, child.childLanes),
); // 收集lanes
subtreeFlags |= child.subtreeFlags; // 将子节点收集的flags记录起来
subtreeFlags |= child.flags; // 以及子节点本身的flags
actualDuration += child.actualDuration; // actualDuration也进行累加
treeBaseDuration += child.treeBaseDuration; // treeBaseDuration进行累加
child = child.sibling; // 通过while+sibling找完同级子节点
}
// 收集完进行赋值
completedWork.actualDuration = actualDuration;
completedWork.treeBaseDuration = treeBaseDuration;
} else {...}
}
completedWork.subtreeFlags |= subtreeFlags; // 收集完进行赋值
} else {....}
completedWork.childLanes = newChildLanes;
return didBailout;
}
十、renderRootConcurrent
搞定整个renderRootSync流程后,我们再继续了解一波renderRootConcurrent。 其基本逻辑和renderRootSync很类似。 核心都是使用了一个do while循环去调用函数。 renderRootSync调用的是workLoopSync而renderRootConcurrent调用的是workLoopConcurrent
js
function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
...
do {
try {
workLoopConcurrent();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
resetContextDependencies();
popDispatcher(prevDispatcher);
executionContext = prevExecutionContext;
if (workInProgress !== null)
return RootInProgress;
} else {
// Completed the tree.
if (enableSchedulingProfiler) {
markRenderStopped();
}
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
return workInProgressRootExitStatus;
}
}
workLoopConcurrent
该函数的逻辑和workLoopSync基本一致。都是开了一个while循环去调用performUnitOfWork。 但是该函数多了一个条件,也就是shouldYield
js
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
shouldYield
shouldYield的作用就是判断是否应该中断当前执行。 如果返回false则表示还有可执行时间。 如果返回true则会暂停执行。 剩下的工作留到下一次宏任务进行处理。 这也就是renderRootConcurrent和renderRootSync区别的核心所在了。 前者可以中断FiberTree的创建而后者无法- 目前的逻辑就是判断间隔的时间, 是否小于
frameInterval(定义为5ms) - 其中还可以存在逻辑通过
enableIsInputPending字段判断是否要依赖其他依据再更精准的进行判断防止高频地打断,减少打断的频率,其中利用了Scheduling/isInputPending, 该API可以检查事件队列中是否有挂起的输入事件,表明用户正试图与页面交互。
js
function shouldYieldToHost() {
const timeElapsed = getCurrentTime() - startTime;
if (timeElapsed < frameInterval) {
return false;
}
if (enableIsInputPending) {
if (needsPaint) {
return true;
}
if (timeElapsed < continuousInputInterval) {
if (isInputPending !== null) {
return isInputPending();
}
} else if (timeElapsed < maxInterval) {
if (isInputPending !== null) {
return isInputPending(continuousOptions);
}
} else
return true;
}
}
return true;
}
- 既然有打断就需要有回复的过程。 我们可以注意到如果当然
workInprogress不为空的情况下, 被打断的话。 此时renderRootConcurrent会返回status为RootInProgress
js
if (workInProgress !== null) return RootInProgress; }
- 接着我们回看
performConcurrentWorkOnRoot的逻辑.可以看到RootInProgress的状态时,就直接调用ensureRootIsSchedule重新开启调度了。 - 如果前后优先级一致的话,
callbackNode不会修改。- 此时会直接
ensureRootIsSchedule会直接return回来。 - 那么此时
performConcurrentWorkOnRoot判断root.callbackNode ==== originialCallbackNode为true, 那么就返回了performConcurrentWorkOnRoot他本身。 - 再继续回看
workLoop逻辑。 如果此时performConcurrentWorkOnRoot返回的是一个函数, 那么会继续赋值给currentTask的callback, 然后继续调用。不过workLoop同样存在shouldYield逻辑。故最后可能也是走向了重新创建宏任务执行 - 此时
workInProgress不会重置, 还是从上次暂停的地方继续开始构建。
- 此时会直接
- 如果有更高优先级的任务。 那么此时就按照
scheduleCallback去进行任务调度了
此部分内容需要结构第二章一起看比较清晰

十一、总结
react fiberTree的创建过程逻辑主要采用的是DFS深度优先遍历。 beginwork自上而下, 逐层地根据element创建对应的Fiber节点。 当走到底的时候,再通过completeWork进行自下而上的回找完善。当然completeWork也承担了比如说创建DOM节点, 冒泡DOM,lanes, flags等任务。 上述两个过程是交替执行的, 当workInProgress为null的时候说明Fiber树已经构建完毕。如果调用的是renderRootConcurrent那么整个Fiber构建的过程都是可以被打断的。
这一部分内容实在太多了,因为不同的Fiber节点对应的方法都存在差异,包括挂载和更新也存在差异。并发和同步也存在差异。 render过程是一个很庞大的过程。故此文章基本跟着调试走,理解一些主要的逻辑,然后尽可能的多发散一些逻辑。 后面会再更细致地处理比如函数组件如何处理, 类组件如何处理, hook如何处理。