render 阶段主要做什么
在 render 阶段,React 可以根据当前可用的时间片处理一个或多个 fiber 节点,并且得益于 fiber 对象中存储的元素上下文信息以及指针域构成的链表结构,使其能够将执行到一半的工作保存在内存的链表中。当 React 停止并完成保存的工作后,让出时间片去处理一些其他优先级更高的事情。之后,在重新获取到可用的时间片后,它能够根据之前保存在内存的上下文信息通过快速遍历的方式找到停止的 fiber 节点并继续工作。由于在此阶段执行的工作并不会导致任何用户可见的更改,因为并没有被提交到真实的 DOM。所以,我们说是 fiber 让调度能够实现暂停、中止以及重新开始等增量渲染的能力;
总结一下, render 阶段主要有以下主要任务:
- 更新任务的触发,以及创建更新任务;
- Reconciler 中通过 diff 算法找出需要更新的组件并打上 effectTag,以及注册调度任务、执行任务回调与渲染器(react-dom) 交互, 渲染 DOM 节点;
- scheduler 根据帧空闲时间调度任务以及中断任务;
其实上面的主要任务也是围绕着下面两个问题来进行的:
- Fiber 节点是如何被创建并构建 Fiber 树的;
- 触发状态更新的方法是如何完成工作的;
接下来的部分主要根据 Fiber 节点是如何被创建并构建 Fiber 树的来进行代码的解析;有关状态更新可以查看这篇文章;
Fiber 树构建流程
render 阶段开始于 performSyncWorkOnRoot 或 performConcurrentWorkOnRoot 方法的调用,这两个方法的差异在于一个是同步,而另一个是异步可中断(concurrent 模式)的。
这两个方法分别会调用下面两个方法—— workLoopSync 和 workLoopConcurrent:
// performSyncWorkOnRoot 会调用该方法
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
// performConcurrentWorkOnRoot 会调用该方法
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
可以看到,他们唯一的区别是是否调用 shouldYield。如果当前浏览器帧没有剩余时间,shouldYield 会中止循环,直到浏览器有空闲时间后再继续遍历;workInProgress 代表当前已创建的 workInProgress fiber。
通过方法中的 while 循环,Reconciler 将通过“深度遍历”的策略来完成对整棵 workInProgress Fiber Tree 的创建。
performUnitOfWork 方法会创建下一个 Fiber 节点并赋值给 workInProgress,如果下一个节点为 null 不存在,则认为执行结束退出 workLoop 循环并准备进行一次提交更改;并将 workInProgress 与已创建的 Fiber 节点连接起来构成 Fiber 树。
在 React 渲染流程中讲到 Fiber Reconciler 是从 Stack Reconciler 重构而来,通过遍历的方式实现可中断的递归,所以 performUnitOfWork 的工作可以分为两部分:“递”和“归”,这两部分主要分别对应 beginWork、completeWork 方法;
performUnitOfWork 方法对应代码:
function performUnitOfWork(unitOfWork: Fiber): void {
// ...
const current = unitOfWork.alternate;
// ...
let next;
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
// ...
next = beginWork(current, unitOfWork, subtreeRenderLanes);
} else {
next = beginWork(current, unitOfWork, subtreeRenderLanes);
}
// ...
if (next === null) {
// completeUnitOfWork 中执行 completeWork 方法
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
ReactCurrentOwner.current = null;
}
beginWork
首先从 rootFiber 开始向下深度优先遍历。为遍历到的每个Fiber节点调用 beginWork 方法。该方法会根据传入的Fiber 节点创建子 Fiber 节点,并将这两个 Fiber 节点连接起来,作为下次循环的主体(unitOfWork);当遍历到叶子节点(即没有子组件的组件)时也就是返回 null,也是递归的终止条件,就会进入“归”阶段。
function beginWork(
// 当前组件对应的 Fiber 节点在上一次更新时的 Fiber 节点,即 workInProgress.alternate
current: Fiber | null,
// 当前组件对应的 Fiber 节点
workInProgress: Fiber,
// 优先级相关
renderLanes: Lanes,
): Fiber | null {
...
// 判断 current 是否为 null,不为 null 表示更新,可能存在优化路径,
// 可以复用current(即上一次更新的Fiber节点);
// 为 null 则表示是创建
if (current !== null) {
const oldProps = current.memoizedProps; // 上一次更新的 props
const newProps = workInProgress.pendingProps; // 本次更新的 props
// didReceiveUpdate 表示是否有新的 props 更新,有则会设置为true,没有则是false
if (
oldProps !== newProps ||
hasLegacyContextChanged() ||
// Force a re-render if the implementation changed due to hot reload:
(__DEV__ ? workInProgress.type !== current.type : false)
) {
didReceiveUpdate = true;
} else {
// checkScheduledUpdateOrContext 函数检查当前 fiber 节点上的 lanes,
// 是否存在于 renderLanes 中;
// 存在则说明当前 fiber 节点需要更新,不存在则不需要更新则复用之前的节点
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
if (
!hasScheduledUpdateOrContext &&
(workInProgress.flags & DidCapture) === NoFlags
) {
didReceiveUpdate = false;
// 此时无需更新,拦截无需更新的节点,复用之前的节点
return attemptEarlyBailoutIfNoScheduledUpdate(
current,
workInProgress,
renderLanes,
);
}
// This is a special case that only exists for legacy mode
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
didReceiveUpdate = true;
} else {
didReceiveUpdate = false;
}
}
} else {
didReceiveUpdate = false;
}
workInProgress.lanes = NoLanes;
// mount 时,根据 tag 不同,创建不同的子 Fiber 节点
switch (workInProgress.tag) {
case IndeterminateComponent:
// ...省略
case LazyComponent:
// ...省略
case FunctionComponent:
// ...省略
case ClassComponent:
// ...省略
case HostRoot:
// ...省略
case HostComponent:
// ...省略
case HostText:
// ...省略
// ...省略其他类型
}
}
可以看到在 beginWork 中根据 current === null 来区分组件是处于 mount 还是 update,所以 beginWork 的工作可以分为两部分:
- update时:如果 current 存在,在满足一定条件时可以复用 current 节点,这样就能克隆 current.child 作为workInProgress.child,而不需要新建 workInProgress.child。
- mount时:除 fiberRootNode 以外,current === null。会根据 fiber.tag 不同,创建不同类型的子 Fiber 节点。
update 时
在 update 时,首先需要判断是否可以复用前一次更新的子 Fiber 节点;可以看到,当(didReceiveUpdate === false)满足以下条件时,表示可以复用上一次更新的节点:
- oldProps === newProps && workInProgress.type === current.type,即 props 与 fiber.type 不变。
- !includesSomeLane(renderLanes, updateLanes),即当前 Fiber 节点优先级不够,此时无需更新,拦截无需更新的节点。
然后进入 bailoutOnAlreadyFinishedWork 方法:
function bailoutOnAlreadyFinishedWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
): Fiber | null {
if (current !== null) {
workInProgress.dependencies = current.dependencies;
}
// 标记有跳过的更新
markSkippedUpdateLanes(workInProgress.lanes);
// 如果子节点没有更新,返回 null,终止遍历
if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
return null;
} else {
// 子节点有更新,那么从 current 上复制子节点,并 return 出去
cloneChildFibers(current, workInProgress);
return workInProgress.child;
}
}
- 当 !includesSomeLane(renderLanes, workInProgress.childLanes) === true 时,会直接 return null ,也就说明子节点没有更新,转到本节点的 completeWork 阶段(一个优化路径)。
- 不满足上述条件时,则克隆 current 树上对应的子 Fiber 节点并返回,作为下次 performUnitOfWork 的主体。
这里有关优先级相关的,后面会写一篇 react 中的优先级先关的文章;
cloneChildFibers 代码如下:
export function cloneChildFibers(
current: Fiber | null,
workInProgress: Fiber,
): void {
// 省略
/* 判断子节点为空,则直接返回 */
if (workInProgress.child === null) {
return;
}
let currentChild = workInProgress.child;
// 复用currentChild
let newChild = createWorkInProgress(currentChild, currentChild.pendingProps);
workInProgress.child = newChild;
newChild.return = workInProgress; // 让子 Fiber 节点与当前 Fiber 节点建立联系
/* 遍历子节点的所有兄弟节点并进行节点复用 */
while (currentChild.sibling !== null) {
currentChild = currentChild.sibling;
newChild = newChild.sibling = createWorkInProgress(
currentChild,
currentChild.pendingProps,
);
newChild.return = workInProgress;
}
newChild.sibling = null;
}
这里就是根据当前存在的 workInProgress.child 进行复用节点;
mount 时
当不满足优化路径时,我们就进入第二部分,新建子 Fiber。
我们可以看到,根据 fiber.tag 不同,进入不同类型 Fiber 的创建逻辑。
// mount时:根据 tag 不同,创建不同的 Fiber 节点
switch (workInProgress.tag) {
case IndeterminateComponent:
// ...省略
case LazyComponent:
// ...省略
case FunctionComponent:
// ...省略
case ClassComponent:
// ...省略
case HostRoot:
// ...省略
case HostComponent:
// ...省略
case HostText:
// ...省略
// ...省略其他类型
}
对于我们常见的组件类型,如(FunctionComponent/ClassComponent/HostComponent),最终会进入reconcileChildren 方法,也就进入了之前文章讲到的 diff 算法的部分;
reconcileChildren
主要工作:
- 对于 mount 的组件,他会创建新的子 Fiber 节点
- 对于 update 的组件,他会将当前组件与该组件在上次更新时对应的 Fiber 节点比较(Diff 算法),将比较的结果生成新 Fiber 节点。
详细的代码解析可以看这里;最终他会生成新的子 Fiber 节点并赋值给 workInProgress.child,作为本次 beginWork 返回值,并作为下次 performUnitOfWork 执行时 workInProgress 的传参。
在 reconcileChilden 中,会为生成的 Fiber 节点带上 effectTag 属性;
effectTag 属性
在 React 渲染流程中 render 阶段的工作是在内存中进行,当工作结束后会通知 Renderer 需要执行的 DOM 操作。要执行 DOM 操作的具体类型就保存在 fiber.effectTag 中。例如下面的 tag:
// DOM 需要插入到页面中
export const Placement = /* */ 0b00000000000010;
// DOM 需要更新
export const Update = /* */ 0b00000000000100;
// DOM 需要插入到页面中并更新
export const PlacementAndUpdate = /* */ 0b00000000000110;
// DOM 需要删除
export const Deletion = /* */ 0b00000000001000;
通过二进制表示 effectTag,可以方便的使用位操作为 fiber.effectTag 赋值多个 effect。
如果要通知 Renderer 将 Fiber 节点对应的 DOM 节点插入页面中,需要满足两个条件:
- fiber.stateNode 存在,即 Fiber 节点中保存了对应的 DOM 节点。
- (fiber.effectTag & Placement) !== 0,即 Fiber 节点存在 Placement effectTag。
这里需要注意两个问题:
- 我们知道,mount 时,fiber.stateNode === null,且在 reconcileChildren 中调用的 mountChildFibers 不会为Fiber 节点赋值 effectTag;fiber.stateNode 会在 completeWork 中进行创建;
- 在 mount 时只有 rootFiber 会赋值 Placement effectTag,在 commit 阶段只会执行一次插入操作。
总结
beginWork 整个流程如下:
completeWork
流程概览
workInProgress节点 complete 阶段。这个时候拿到的 workInProgress 节点都是经过 diff 算法调和过的,也就意味着对于某个节点来说它 fiber 的形态已经基本确定了,但除此之外还有两点:
- 目前只有 fiber 形态变了,对于原生 DOM 组件(HostComponent)和文本节点(HostText)的 fiber 来说,对应的 DOM 节点(fiber.stateNode)并未变化。
- 经过 diff 生成的新的 workInProgress 节点持有了 effectTag。
如果说“递”阶段的 beginWork 方法主要是创建子节点,那么“归”阶段的 completeWork 方法则主要是创建当前节点的 DOM 节点,并对子节点的 DOM 节点和 EffectList 进行收拢。所以 completeWork 的工作主要有:
- 构建或更新 DOM 节点,
- 构建过程中,会将子孙 DOM 节点插入刚生成的 DOM 节点中。
- 更新过程中,会计算 DOM 节点的属性,一旦属性需要更新,会为 DOM 节点对应的 workInProgress 节点标记Update 的 effectTag。
- 自下而上收集 effectList,最终收集到 root 上。
类似 beginWork,completeWork 也是针对不同 fiber.tag 调用不同的处理逻辑。
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case IndeterminateComponent:
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:
return null;
case ClassComponent: {
// ...省略
return null;
}
case HostRoot: {
// ...省略
updateHostContainer(workInProgress);
return null;
}
case HostComponent: {
// ...省略
return null;
}
// ...省略
由于 React 的大部分的 fiber 节点最终都要体现为 DOM,所以本文主要分析 HostComponent 即原生 DOM 组件对应的Fiber节点)相关的处理流程。
case HostComponent: {
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
// update的情况
// ...省略
} else {
// mount的情况
// ...省略
}
return null;
}
和 beginWork 一样,我们根据 current === null 判断是 mount 还是 update;同时针对 HostComponent,判断update 时我们还需要考虑 workInProgress.stateNode != null(即该 Fiber 节点是否存在对应的 DOM 节点)。对于 HostComponent 的处理分为 DOM 更新和 DOM 创建两个过程。下面以 DOM 创建(mount)或者 DOM 更新(update) 进行分析如何进行 DOM 节点的插入、更新以及属性的处理。
mount 阶段(DOM 创建)
对于新增的 DOM 节 点,会先创建一个 DOM 节点,之后将该节点挂载到 Fiber 的 stateNode 属性上,然后插入该节点(appendAllChildren)。所以,mount 时的主要逻辑包括三个:
- 为 Fiber 节点生成对应的 DOM 节点。
- 将子孙 DOM 节点插入刚生成的 DOM 节点中。
- 处理 props。
// mount 的情况
// ...省略服务端渲染相关逻辑
const currentHostContext = getHostContext();
// 为 fiber 创建对应 DOM 节点
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
// 将子孙 DOM 节点插入刚生成的 DOM 节点中
appendAllChildren(instance, workInProgress, false, false);
// DOM 节点赋值给 fiber.stateNode
workInProgress.stateNode = instance;
// 处理 props 的过程
if (
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
currentHostContext,
)
) {
markUpdate(workInProgress);
}
update 阶段(DOM 更新)
当 update 时,Fiber 节点已经存在对应 DOM 节点,所以不需要生成 DOM 节点。需要做的主要是处理 props,比如:
- onClick、onChange 等回调函数的注册。
- 处理 style prop。
- 处理 DANGEROUSLY_SET_INNER_HTML prop。
- 处理 children prop。
updateHostComponent 函数负责 HostComponent 对应 DOM 节点属性的更新以及给当前节点打上 Update 的EffectTag:
updateHostComponent = function(
current: Fiber,
workInProgress: Fiber,
type: Type,
newProps: Props,
rootContainerInstance: Container,
) {
const oldProps = current.memoizedProps;
// 新旧 props 相同,不更新
if (oldProps === newProps) {
return;
}
const instance: Instance = workInProgress.stateNode;
const currentHostContext = getHostContext();
// prepareUpdate 计算新属性
const updatePayload = prepareUpdate(
instance,
type,
oldProps,
newProps,
rootContainerInstance,
currentHostContext,
);
// 最终新属性被挂载到 updateQueue 中,供 commit 阶段使用
workInProgress.updateQueue = (updatePayload: any);
if (updatePayload) {
// 标记 workInProgress 节点有更新
markUpdate(workInProgress);
}
};
上面代码可以看到,被处理完的 props 会被赋值给 workInProgress.updateQueue,并最终会在 commit 阶段被渲染在页面上(操作 DOM 实现页面渲染过程)。
其中 updatePayload 为数组形式,他的偶数索引的值为变化的 prop key,奇数索引的值为变化的 prop value。
比如页面中有一个 p 标签:
import { useState } from 'react'
function App() {
const [num, add] = useState(0);
return (
<p onClick={() => add(num + 1)}>{num}</p>
)
}
export default App
点击 p 标签更新后,可以看到此时 updatePayload:
这个结果由 prepareUpdate 中的 diffProperties 方法进行计算产生,它对比 lastProps 和 nextProps,计算出 updatePayload。
diffProperties 内部的规则可以概括为:
- 如果为新增,将新增的 key 以及对应的 value 添加到 updatePayload 中;
- 如果为修改,将修改后对应的 value 添加到 updatePayload 中;
- 如果为删除,将对应属性的 value 标记为 null 表述删除。
具体工作:
- 对特定 tag (由于本场景是处理 HostComponent ,因此 tag 即 html 标签名)的 lastProps & nextProps 做特殊处理,包括 input/select/textarea ,举例:input 的 value 值可能会是个 number ,而原生 input 的 value 只接受 string,因此这里需要转换数据类型。
- 遍历 lastProps:
- 如果该 prop 在 nextProps 中也存在,那么就跳过,相当于该 prop 没有变化,无需处理。
- 见到有 style 的 prop 就整理到 styleUpdates 变量(object)中,这部分 style 属性被置为空值
- 把除以上情况外的 propKey 推进一个数组(updatePayload)中,另外再推一个 null 值进数组中,表示把该 prop 清空掉。
- 遍历 nextProps:
- 如果该 nextProp 与 lastProp 一致,即更新前后没有发生变化,则跳过。
- 见到有 style 的 prop 就整理到 styleUpdates 变量中,注意这部分 style 属性是有值的
- 处理 DANGEROUSLY_SET_INNER_HTML
- 处理 children
- 除以上场景外,直接把 prop 的 key 和值都推进数组(updatePayload)中。
- 如果 styleUpdates 不为空,那么就把’style’和 styleUpdates 变量都推进数组(updatePayload)中。
- 返回 updatePayload。
代码如下:
export function diffProperties(
domElement: Element,
tag: string,
lastRawProps: Object,
nextRawProps: Object,
rootContainerElement: Element | Document,
): null | Array<mixed> {
let updatePayload: null | Array<any> = null;
let lastProps: Object;
let nextProps: Object;
...
let propKey;
let styleName;
let styleUpdates = null;
for (propKey in lastProps) {
// 循环 lastProps,找出需要标记删除的 propKey
if (
nextProps.hasOwnProperty(propKey) ||
!lastProps.hasOwnProperty(propKey) ||
lastProps[propKey] == null
) {
// 对 propKey 来说,如果 nextProps 也有,或者 lastProps 没有,那么
// 就不需要标记为删除,跳出本次循环继续判断下一个 propKey
continue;
}
if (propKey === STYLE) {
// 删除 style
const lastStyle = lastProps[propKey];
for (styleName in lastStyle) {
if (lastStyle.hasOwnProperty(styleName)) {
if (!styleUpdates) {
styleUpdates = {};
}
styleUpdates[styleName] = '';
}
}
} else if(/*...*/) {
...
// 一些特定种类的 propKey 的删除
} else {
// 将其他种类的 propKey 标记为删除
(updatePayload = updatePayload || []).push(propKey, null);
}
}
for (propKey in nextProps) {
// 将新 prop 添加到 updatePayload
const nextProp = nextProps[propKey];
const lastProp = lastProps != null ? lastProps[propKey] : undefined;
if (
!nextProps.hasOwnProperty(propKey) ||
nextProp === lastProp ||
(nextProp == null && lastProp == null)
) {
// 如果 nextProps 不存在 propKey,或者前后的 value 相同,或者前后的 value 都为 null
// 那么不需要添加进去,跳出本次循环继续处理下一个 prop
continue;
}
if (propKey === STYLE) {
/*
* lastProp: { color: 'red' }
* nextProp: { color: 'blue' }
* */
// 如果 style 在 lastProps 和 nextProps 中都有
// 那么需要删除 lastProps 中 style 的样式
if (lastProp) {
// 如果 lastProps 中也有 style
// 将 style 内的样式属性设置为空
// styleUpdates = { color: '' }
for (styleName in lastProp) {
if (
lastProp.hasOwnProperty(styleName) &&
(!nextProp || !nextProp.hasOwnProperty(styleName))
) {
if (!styleUpdates) {
styleUpdates = {};
}
styleUpdates[styleName] = '';
}
}
// 以 nextProp 的属性名为 key 设置新的 style 的 value
// styleUpdates = { color: 'blue' }
for (styleName in nextProp) {
if (
nextProp.hasOwnProperty(styleName) &&
lastProp[styleName] !== nextProp[styleName]
) {
if (!styleUpdates) {
styleUpdates = {};
}
styleUpdates[styleName] = nextProp[styleName];
}
}
} else {
// 如果 lastProps 中没有 style,说明新增的
// 属性全部可放入 updatePayload
if (!styleUpdates) {
if (!updatePayload) {
updatePayload = [];
}
updatePayload.push(propKey, styleUpdates);
// updatePayload: [ style, null ]
}
styleUpdates = nextProp;
// styleUpdates = { color: 'blue' }
}
} else if (/*...*/) {
...
// 一些特定种类的 propKey 的处理
} else if (registrationNameDependencies.hasOwnProperty(propKey)) {
if (nextProp != null) {
// 重新绑定事件
ensureListeningTo(rootContainerElement, propKey);
}
if (!updatePayload && lastProp !== nextProp) {
// 事件重新绑定后,需要赋值updatePayload,使这个节点得以被更新
updatePayload = [];
}
} else if (
typeof nextProp === 'object' &&
nextProp !== null &&
nextProp.$$typeof === REACT_OPAQUE_ID_TYPE
) {
// 服务端渲染相关
nextProp.toString();
} else {
// 将计算好的属性 push 到 updatePayload
(updatePayload = updatePayload || []).push(propKey, nextProp);
}
}
if (styleUpdates) {
// 将 style 和值 push 进 updatePayload
(updatePayload = updatePayload || []).push(STYLE, styleUpdates);
}
return updatePayload;
}
具体的渲染过程后面在 commit 阶段会详细分析;
总结
completeWork 整体代码如下:
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case IndeterminateComponent:
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:
return null;
case ClassComponent: {
// ...省略
return null;
}
case HostRoot: {
// ...省略
return null;
}
case HostComponent: {
// ...
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
// 更新 dom 节点
updateHostComponent(
current,
workInProgress,
type,
newProps,
rootContainerInstance,
);
if (enableDeprecatedFlareAPI) {
const prevListeners = current.memoizedProps.DEPRECATED_flareListeners;
const nextListeners = newProps.DEPRECATED_flareListeners;
if (prevListeners !== nextListeners) {
markUpdate(workInProgress);
}
}
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
//...
const currentHostContext = getHostContext();
const wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
// 服务端渲染相关
} else {
// 创建新的dom节点
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
// 把fiber子节点的dom挂载到当前dom后面
appendAllChildren(instance, workInProgress, false, false);
workInProgress.stateNode = instance;
if (enableDeprecatedFlareAPI) {
const listeners = newProps.DEPRECATED_flareListeners;
if (listeners != null) {
updateDeprecatedEventListeners(
listeners,
workInProgress,
rootContainerInstance,
);
}
}
if (
// 初始化dom属性和事件
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
currentHostContext,
)
) {
markUpdate(workInProgress);
}
}
if (workInProgress.ref !== null) {
markRef(workInProgress);
}
}
return null;
}
// ...省略
}
整体流程图如下:
completeUnitOfWork 源码
function completeUnitOfWork(unitOfWork: Fiber): void {
// 尝试完成当前的工作单元,然后移动到下一个兄弟节点。
// 如果没有更多的兄弟节点,则返回到父级 fiber,最终返回 root。
let completedWork = unitOfWork;
do {
// 获取当前节点
const current = completedWork.alternate;
//获取父节点
const returnFiber = completedWork.return;
// 判断节点的操作是否完成,还是有异常丢出
// Incomplete 表示捕获到该节点抛出的 error
// & 是表示位的与运算,把左右两边的数字转化为二进制,然后每一位分别进行比较,如果相等就为1,不相等即为0
//如果该节点没有异常抛出的话,即可正常执行
if ((completedWork.flags & Incomplete) === NoFlags) {
setCurrentDebugFiberInDEV(completedWork);
let next;
// 如果不能使用分析器的 timer 的话,直接执行 completeWork,
// 否则执行分析器 timer,并执行 completeWork
if (
!enableProfilerTimer ||
(completedWork.mode & ProfileMode) === NoMode
) {
next = completeWork(current, completedWork, subtreeRenderLanes);
} else {
// 启动分析器的定时器,并赋成当前时间
startProfilerTimer(completedWork);
// 完成该节点的更新
next = completeWork(current, completedWork, subtreeRenderLanes);
// 在没有报错的前提下,更新渲染持续时间
stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
}
resetCurrentDebugFiberInDEV();
if (next !== null) {
// 如果 next 存在,则表示产生了新 work.
workInProgress = next;
return;
}
// 如果该 fiber 节点未能完成 work 的话(报错)
} else {
// 节点未能完成更新,捕获其中的错误
const next = unwindWork(completedWork, subtreeRenderLanes);
// 由于该 fiber 未能完成,所以不必重置它的 lanes
//如果 next 存在,则表示产生了新 work
if (next !== null) {
//更新其 effectTag,标记是 restart 的
next.flags &= HostEffectMask;
// 返回 next,以便执行新 work
workInProgress = next;
return;
}
if (
enableProfilerTimer &&
(completedWork.mode & ProfileMode) !== NoMode
) {
// 记录出错 fiber 的渲染持续时间。
stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
// 虽然报错了,但仍然会累计 work 时长
let actualDuration = completedWork.actualDuration;
let child = completedWork.child;
while (child !== null) {
actualDuration += child.actualDuration;
child = child.sibling;
}
completedWork.actualDuration = actualDuration;
}
// 将父 fiber 节点标记为不完整并清除其子树标志
if (returnFiber !== null) {
returnFiber.flags |= Incomplete;
returnFiber.subtreeFlags = NoFlags;
returnFiber.deletions = null;
}
}
// 获取兄弟节点
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
// If there is more work to do in this returnFiber, do that next.
workInProgress = siblingFiber;
return;
}
// 如果能执行到这一步的话,说明 siblingFiber 为 null,
// 那么就返回至父节点
completedWork = returnFiber;
// Update the next thing we're working on in case something throws.
workInProgress = completedWork;
} while (completedWork !== null);
// We've reached the root.
if (workInProgressRootExitStatus === RootIncomplete) {
workInProgressRootExitStatus = RootCompleted;
}
}
参考
https://juejin.cn/post/6859528127010471949
https://juejin.cn/post/7019254208830373902
https://juejin.cn/post/6919629280012042254
https://juejin.cn/post/7017703203525361695
https://react.iamkasong.com/state/reactdom.html