这次是接着上一期render阶段的文章,解析挂载时render阶段的"归"阶段----completeWork函数
completeWork开始阶段
在performUnitOfWork中执行完beginWork,就会进入下面判断
if (next === null) {
// workInProgress已经不存在子树,就开始进行"归"阶段
completeUnitOfWork(unitOfWork);
} else {
// next是beginWork调用后的返回值workInProgress.child
workInProgress = next;
}
completeUnitOfWork
主要做了两件事,执行completeWork 和收拢EffectList
function completeUnitOfWork(unitOfWork: Fiber): void {
//尝试完成当前的工作单元,然后移动到下一个兄弟。 如果没有更多的兄弟,返回到父fiber。
let completedWork = unitOfWork;
// 直到父节点为null,表示整棵 workInProgress fiber 树已处理完毕。
do {
// 记录父节点和当前节点的current树
const current = completedWork.alternate;
const returnFiber = completedWork.return;
// 检查工作是否完成或是否有东西抛出.
if ((completedWork.effectTag & Incomplete) === NoEffect) {
let next;
if (
!enableProfilerTimer ||
(completedWork.mode & ProfileMode) === NoMode
) {
// 执行completeWork,并把返回值赋值给next
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 &&
(returnFiber.effectTag & Incomplete) === NoEffect
) {
// 执行effect相关
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;
}
if (effectTag > PerformedWork) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork;
} else {
returnFiber.firstEffect = completedWork;
}
returnFiber.lastEffect = completedWork;
}
}
} else {
const next = unwindWork(completedWork, subtreeRenderLanes);
// Because this fiber did not complete, don't reset its expiration time.
if (next !== null) {
next.effectTag &= HostEffectMask;
workInProgress = next;
return;
}
if (
enableProfilerTimer &&
(completedWork.mode & ProfileMode) !== NoMode
) {
stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
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 effect list.
returnFiber.firstEffect = returnFiber.lastEffect = null;
returnFiber.effectTag |= Incomplete;
}
}
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
// If there is more work to do in this returnFiber, do that next.
workInProgress = siblingFiber;
return;
}
// 赋值父节点
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
// We've reached the root.
if (workInProgressRootExitStatus === RootIncomplete) {
workInProgressRootExitStatus = RootCompleted;
}
}
运行流程
completeWork
如果说“递”阶段的 beginWork 方法主要是创建子节点,那么“归”阶段的 completeWork 方法则主要是创建当前节点的 DOM 节点,并对子节点的 DOM 节点和 EffectList 进行收拢。很多类型是不进行处理,return null
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;
}
// ...省略
}
createInstance
新建节点,调用createElement创建dom节点
function createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): Instance {
let parentNamespace: string;
if (__DEV__) {
// TODO: take namespace into account when validating.
const hostContextDev = ((hostContext: any): HostContextDev);
validateDOMNesting(type, null, hostContextDev.ancestorInfo);
if (
typeof props.children === 'string' ||
typeof props.children === 'number'
) {
const string = '' + props.children;
const ownAncestorInfo = updatedAncestorInfo(
hostContextDev.ancestorInfo,
type,
);
validateDOMNesting(null, string, ownAncestorInfo);
}
parentNamespace = hostContextDev.namespace;
} else {
parentNamespace = ((hostContext: any): HostContextProd);
}
const domElement: Instance = createElement(
type,
props,
rootContainerInstance,
parentNamespace,
);
precacheFiberNode(internalInstanceHandle, domElement);
// 更新属性
updateFiberProps(domElement, props);
return domElement;
}
appendAllChildren
把fiber子节点的dom挂载到当前dom后面
appendAllChildren = function(
parent: Instance,
workInProgress: Fiber,
needsVisibilityToggle: boolean,
isHidden: boolean,
) {
let node = workInProgress.child;
while (node !== null) {
if (node.tag === HostComponent || node.tag === HostText) {
// stateNode挂载节点的dom
appendInitialChild(parent, node.stateNode);
} else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
appendInitialChild(parent, node.stateNode.instance);
} else if (node.tag === HostPortal) {
} else if (node.child !== null) {
// 针对一些特殊类型的子节点,如<Fragment />,尝试从子节点的子节点获取DOM
// 存在子节点就继续遍历子节点
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.sibling作为下次循环的主体
node = node.sibling;
}
};
// 执行了原生的appendChild方法
export function appendInitialChild(parentInstance: Instance, child: Instance | TextInstance): void {
parentInstance.appendChild(child);
}
updateHostComponent
更新旧的dom节点,主要作用就是计算出需要变化的 DOM 节点属性,并给当前节点打上Update的EffectTag。
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;
// props没有变化就直接返回
if (oldProps === newProps) {
return;
}
const updatePayload = prepareUpdate(
instance,
type,
oldProps,
newProps,
rootContainerInstance,
currentHostContext,
);
// 将计算出来的updatePayload挂载在workInProgress.updateQueue上,供后续commit阶段使用
workInProgress.updateQueue = (updatePayload: any);
// 如果updatePayload不为空,则给当前节点打上Update的EffectTag
if (updatePayload) {
markUpdate(workInProgress);
}
};
总结
- completeUnitOfWork方法主要循环执行completeWork,父元素为空或者存在兄弟节点就会进行下一轮render阶段解析,生成兄弟节点的fiber。
- completeWork主要是生成当前fiber的dom节点,并且挂载连接子节点的dom
- completeWork主要使用createInstance新建节点和updateHostComponent更新节点操作。
- 最终结束completeUnitOfWork执行,进入commit阶段(下个文章开始讲,敬请期待)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。