目录
2021SC@SDUSC
删除操作的入口以及相关顺序
在commitMutationEffects正式完成对DOM节点的操作,在commitMutationEffects_begin中,将nextEffect初始化为root,然后循环删除其子节点,当子节点为空值时,调用commitMutationEffects_complete,在该方法中首先对Fiber节点进行相对应的更新与插入操作,然后检查是否有兄弟节点,如果存在兄弟节点就将兄弟节点赋值给nexteffect并返回继续由 ommitMutationEffects_begin进行处理(寻找可以删除的节点),假如没有兄弟节点,就将nextEffect赋值为父节点,并继续在commitMutationEffects_complete中处理。这样既可以确保在完成删除操作之后再进行插入或者更新操作。(先执行删除操作是为了减混乱,提高构建以及更新DOM树的正确性)。
function commitMutationEffects_begin(root: FiberRoot) {
while (nextEffect !== null) {
const fiber = nextEffect;
// TODO: Should wrap this in flags check, too, as optimization
const deletions = fiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
try {
commitDeletion(root, childToDelete, fiber);
} catch (error) {
reportUncaughtErrorInDEV(error);
captureCommitPhaseError(childToDelete, fiber, error);
}
}
}
const child = fiber.child;
if ((fiber.subtreeFlags & MutationMask) !== NoFlags && child !== null) {
ensureCorrectReturnPointer(child, fiber);
nextEffect = child;
} else {
commitMutationEffects_complete(root);
}
}
}
function commitMutationEffects_complete(root: FiberRoot) {
while (nextEffect !== null) {
const fiber = nextEffect;
setCurrentDebugFiberInDEV(fiber);
try {
commitMutationEffectsOnFiber(fiber, root);
} catch (error) {
reportUncaughtErrorInDEV(error);
captureCommitPhaseError(fiber, fiber.return, error);
}
resetCurrentDebugFiberInDEV();
const sibling = fiber.sibling;
if (sibling !== null) {
ensureCorrectReturnPointer(sibling, fiber.return);
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
删除操作的内部流程
现在可以正式进行删除操作,首先都需要找到该目标节点中为DOM节点的父节点(Fiber树节点通常比DOM树多),假如目标节点为DOM节点,那么可以在遍历消除其Fiber子节点后(调用commitNestedUnmounts),利用父节点将该子节点删除。假如目标节点并非DOM节点,那么就需要遍历其子节点(此时调用了commitUnmount为了消除自身Fiber节点)直到找到DOM节点为止,找到后消除其子节点并将该DOM节点删除。
被删除DOM节点的类型可能有HostComponent,HostText,DehydratedFragment,HostPortal。大部分使用 commitUnmount 删除其子节点。在其中可以看到许多组件的卸载过程,比如函数组件中useEffect的清除函数(使用return关键字),比如类组件中的生命周期函数componentWillUnmount,以及 HostComponent(衍生标签节点)删除自身的ref等。
谈谈commit的Mutation阶段中的几个具体方法
首先是commitResetTextContent,该方法作用为清空节点的文本内容。该方法会调用 resetTextContent方法,而resetTextContent则会调用setTextContent方法,在该方法中会检验text文本是否存在,如果存在并且节点的子节点只有一个并且为文本节点,那么就将文本节点的nodeValue设置为text,如果text节点不存在,直接设置节点的textContent为text,而我们传入的text节点为‘’,这里测试了一下
所以该方法中会将textContent设置为指定文本。
再来看一下commitDetachRef方法,这里给出源码
function commitDetachRef(current: Fiber) {
const currentRef = current.ref;
if (currentRef !== null) {
if (typeof currentRef === 'function') {
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
current.mode & ProfileMode
) {
try {
startLayoutEffectTimer();
currentRef(null);
} finally {
recordLayoutEffectDuration(current);
}
} else {
currentRef(null);
}
} else {
currentRef.current = null;
}
}
}
假如节点有ref需要清空,那就执行该方法。如果ref是函数类型就会查看是否能开启定时器,能就开启进行计时,关于react中各种过期与更新时间的具体作用,我将会在以后展开描述。假如并非函数类型。就会直接将ref指向进行清空。
总结
这里介绍了在commit操作树上节点时删除,插入,更新的执行顺序与过程,重点介绍了删除过程中的内部流程。