上一篇可以了解到,组件执行beginWork后会创建子Fiber节点,节点上可能存在Effects List。
方法调用栈如下: performUnitOfWork —> beginWork—> updateClassComponent —> finishedComponent —> completeUnitOfWork
在performUnitOfWork方法中,如果遍历完创建了所有的Fiber节点,就进行completeUnitOfWork方法,创建对应的DOM节点。
if (next === null) {// If this doesn't spawn new work, complete the current work.completeUnitOfWork(unitOfWork);}
接下来看一下 completeUnitOfWork方法 :
function completeUnitOfWork(unitOfWork: Fiber): void {//准备完成当前Fiber节点的工作,完成之后就准备完成下一个兄弟Fiber节点的工作,如果没有兄弟Fiber节点,那就回到父Fiber节点继续工作。let completedWork = unitOfWork;do {const current = completedWork.alternate;const returnFiber = completedWork.return;// Check if the work completed or if something threw.if ((completedWork.flags & Incomplete) === NoFlags) {setCurrentDebugFiberInDEV(completedWork);let next;if (!enableProfilerTimer ||(completedWork.mode & ProfileMode) === NoMode) {next = completeWork(current, completedWork, subtreeRenderLanes);} else {startProfilerTimer(completedWork);next = completeWork(current, completedWork, subtreeRenderLanes);// Update render duration assuming we didn't error.stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);}resetCurrentDebugFiberInDEV();if (next !== null) {// Completing this fiber spawned new work. Work on that next.workInProgress = next;return;}resetChildLanes(completedWork);if (returnFiber !== null &&// Do not append effects to parents if a sibling failed to complete(returnFiber.flags & Incomplete) === NoFlags) {// Append all the effects of the subtree and this fiber onto the effect// list of the parent. The completion order of the children affects the// side-effect order.if (returnFiber.firstEffect === null) {returnFiber.firstEffect = completedWork.firstEffect;}if (completedWork.lastEffect !== null) {if (returnFiber.lastEffect !== null) {returnFiber.lastEffect.nextEffect = completedWork.firstEffect;}returnFiber.lastEffect = completedWork.lastEffect;}const flags = completedWork.flags;// Skip both NoWork and PerformedWork tags when creating the effect// list. PerformedWork effect is read by React DevTools but shouldn't be// committed.if (flags > PerformedWork) {if (returnFiber.lastEffect !== null) {returnFiber.lastEffect.nextEffect = completedWork;} else {returnFiber.firstEffect = completedWork;}returnFiber.lastEffect = completedWork;}}} else {// This fiber did not complete because something threw. Pop values off// the stack without entering the complete phase. If this is a boundary,// capture values if possible.const next = unwindWork(completedWork, subtreeRenderLanes);// Because this fiber did not complete, don't reset its expiration time.if (next !== null) {// If completing this work spawned new work, do that next. We'll come// back here again.// Since we're restarting, remove anything that is not a host effect// from the effect tag.next.flags &= HostEffectMask;workInProgress = next;return;}if (enableProfilerTimer &&(completedWork.mode & ProfileMode) !== NoMode) {// Record the render duration for the fiber that errored.stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);// Include the time spent working on failed children before continuing.let actualDuration = completedWork.actualDuration;let child = completedWork.child;while (child !== null) {actualDuration += child.actualDuration;child = child.sibling;}completedWork.actualDuration = actualDuration;}if (returnFiber !== null) {// Mark the parent fiber as incomplete and clear its subtree flags.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;}// Otherwise, return to the parentcompletedWork = 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;}}
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:bubbleProperties(workInProgress);return null;case ClassComponent: {const Component = workInProgress.type;if (isLegacyContextProvider(Component)) {popLegacyContext(workInProgress);}bubbleProperties(workInProgress);return null;}case HostRoot: {const fiberRoot = (workInProgress.stateNode: FiberRoot);//...updateHostContainer(current, workInProgress);bubbleProperties(workInProgress);return null;}case HostComponent: {//...}case HostText: {//...}case SuspenseComponent: {//...}
这里先讲解页面渲染所必须的HostComponent(即原生DOM组件对应的Fiber节点)
和beginWork一样,根据current === null来 判断是mount还是update。
case HostComponent: {popHostContext(workInProgress);const rootContainerInstance = getRootHostContainer();const type = workInProgress.type;if (current !== null && workInProgress.stateNode != null) {updateHostComponent(current,workInProgress,type,newProps,rootContainerInstance,);if (current.ref !== workInProgress.ref) {markRef(workInProgress);}} else {if (!newProps) {invariant(workInProgress.stateNode !== null,'We must have new props for new mounts. This error is likely ' +'caused by a bug in React. Please file an issue.',);// This can happen when we abort work.bubbleProperties(workInProgress);return null;}const currentHostContext = getHostContext();// TODO: Move createInstance to beginWork and keep it on a context// "stack" as the parent. Then append children as we go in beginWork// or completeWork depending on whether we want to add them top->down or// bottom->up. Top->down is faster in IE11.const wasHydrated = popHydrationState(workInProgress);if (wasHydrated) {// TODO: Move this and createInstance step into the beginPhase// to consolidate.if (prepareToHydrateHostInstance(workInProgress,rootContainerInstance,currentHostContext,)) {// If changes to the hydrated node need to be applied at the// commit-phase we mark this as such.markUpdate(workInProgress);}} else {const instance = createInstance(type,newProps,rootContainerInstance,currentHostContext,workInProgress,);appendAllChildren(instance, workInProgress, false, false);workInProgress.stateNode = instance;if (finalizeInitialChildren(instance,type,newProps,rootContainerInstance,currentHostContext,)) {markUpdate(workInProgress);}}if (workInProgress.ref !== null) {// If there is a ref on a host node we need to schedule a callbackmarkRef(workInProgress);}}bubbleProperties(workInProgress);return null;
update时
updateHostComponent = function(current: Fiber,workInProgress: Fiber,type: Type,newProps: Props,rootContainerInstance: Container,) {// If we have an alternate, that means this is an update and we need to// schedule a side-effect to do the updates.const oldProps = current.memoizedProps;if (oldProps === newProps) {// In mutation mode, this is sufficient for a bailout because// we won't touch this node even if children changed.return;}// If we get updated because one of our children updated, we don't// have newProps so we'll have to reuse them.// TODO: Split the update API as separate for the props vs. children.// Even better would be if children weren't special cased at all tho.const instance: Instance = workInProgress.stateNode;const currentHostContext = getHostContext();// TODO: Experiencing an error where oldProps is null. Suggests a host// component is hitting the resume path. Figure out why. Possibly// related to `hidden`.const updatePayload = prepareUpdate(instance,type,oldProps,newProps,rootContainerInstance,currentHostContext,);// TODO: Type this specific to this type of component.workInProgress.updateQueue = (updatePayload: any);// If the update payload indicates that there is a change or if there// is a new ref we mark this as an update. All the work is done in commitWork.if (updatePayload) {markUpdate(workInProgress);}};
如果是update并且此workInProgress Fiber节点对应的原生DOM节点存在,就不需要生成DOM节点,只需要对props进行处理就行了。这里的props主要是:
onClick、onChange等回调函数的注册- 处理
style prop - 处理
DANGEROUSLY_SET_INNER_HTML prop - 处理
children prop
被处理完的props会被存入workInProgress.updateQueue,并最终会在commit阶段被渲染在页面上。
import React, { useState } from "./react";import ReactDOM from "./react-dom";const rootElement = document.getElementById("root");function App() {const [count, updateCount] = useState(0);return (<h3 title={count} dev={count} onClick={() => updateCount(count + 1)}><p>hello word</p></h3>);}window.ReactDOM.render(<App />, rootElement);
点击区域,更新count的值,h3的props发生改变,改变内容为updatePayload。
其中updatePayload为数组形式,他的偶数索引的值为变化的prop key,奇数索引的值为变化的prop value。

mount时
// 为Fiber创建对应DOM节点const instance = createInstance(type,newProps,rootContainerInstance,currentHostContext,workInProgress,);// 将子孙DOM节点插入刚生成的DOM节点中appendAllChildren(instance, workInProgress, false, false);// DOM节点赋值给fiber.stateNodeworkInProgress.stateNode = instance;//处理propsif (finalizeInitialChildren(instance,type,newProps,rootContainerInstance,currentHostContext,)) {markUpdate(workInProgress);}
completeWork 会从子Fiber节点归到rootFiber节点,每次调用appendAllChildren时都会将已生成的子孙DOM节点插入当前生成的DOM节点下。那么递归到rootFiber时,我们已经有一个构建好的离屏DOM树。
构建完DOM树后,render阶段全部工作完成。在performSyncWorkOnRoot函数中fiberRootNode被传递给mommitRoot方法,开启commit阶段工作流程。
所以completeWork工作流程如下:
