- 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如何处理。