1 )概述
commitDeletion
,这里面做什么呢?2 )源码
定位到 packages/react-reconciler/src/ReactFiberCommitWork.js#L1021
查看 commitDeletion
function commitDeletion(current: Fiber): void {
// dom 环境 默认 true
if (supportsMutation) {
// Recursively delete all host nodes from the parent.
// Detach refs and call componentWillUnmount() on the whole subtree.
unmountHostComponents(current);
} else {
// Detach refs and call componentWillUnmount() on the whole subtree.
commitNestedUnmounts(current);
}
detachFiber(current);
}
进入 unmountHostComponents
function unmountHostComponents(current): void {
// We only have the top Fiber that was deleted but we need recurse down its
// children to find all the terminal nodes.
let node: Fiber = current;
// Each iteration, currentParent is populated with node's host parent if not
// currentParentIsValid.
let currentParentIsValid = false;
// Note: these two variables *must* always be updated together.
let currentParent;
let currentParentIsContainer;
while (true) {
if (!currentParentIsValid) {
let parent = node.return;
// 进入while循环
findParent: while (true) {
invariant(
parent !== null,
'Expected to find a host parent. This error is likely caused by ' +
'a bug in React. Please file an issue.',
);
// 如果它们是这 3 个之一,它们就会跳出这个 while 循环
switch (parent.tag) {
case HostComponent:
currentParent = parent.stateNode;
currentParentIsContainer = false;
break findParent; // 注意,break的是上面对应的while循环,而非当前 switch, 下同如此
case HostRoot:
currentParent = parent.stateNode.containerInfo;
currentParentIsContainer = true;
break findParent;
case HostPortal:
currentParent = parent.stateNode.containerInfo;
currentParentIsContainer = true;
break findParent;
}
// 没有符合条件的,向上去找
parent = parent.return;
}
// 跳出这个while循环之后,他就会设置 parentparentisvalid 为 true
currentParentIsValid = true;
}
if (node.tag === HostComponent || node.tag === HostText) {
commitNestedUnmounts(node);
// After all the children have unmounted, it is now safe to remove the
// node from the tree.
// 上面操作的 currentParentIsContainer 变量,执行不同的 remove 方法,确定从哪里删掉
if (currentParentIsContainer) {
removeChildFromContainer((currentParent: any), node.stateNode); // 从container中删除
} else {
removeChild((currentParent: any), node.stateNode); // 从父节点中删除
}
// Don't visit children because we already visited them.
} else if (node.tag === HostPortal) {
// When we go into a portal, it becomes the parent to remove from.
// We will reassign it back when we pop the portal on the way up.
currentParent = node.stateNode.containerInfo;
currentParentIsContainer = true;
// Visit children because portals might contain host components.
if (node.child !== null) {
node.child.return = node;
node = node.child;
continue; // 找到 child
}
} else {
commitUnmount(node);
// Visit children because we may find more host components below.
if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
}
// 整棵树遍历完了,回到了顶点,结束
if (node === current) {
return;
}
// 树没有兄弟节点,向上去寻找
// 进入了这个循环,说明一侧的子树找完了,开始找兄弟节点了
while (node.sibling === null) {
if (node.return === null || node.return === current) {
return;
}
node = node.return; // 向上寻找
if (node.tag === HostPortal) {
// When we go out of the portal, we need to restore the parent.
// Since we don't keep a stack of them, we will search for it.
currentParentIsValid = false;
}
}
// 在循环的最外面,找兄弟节点
node.sibling.return = node.return;
node = node.sibling; // 找兄弟节点
}
}
commitUnmount
// User-originating errors (lifecycles and refs) should not interrupt
// deletion, so don't let them throw. Host-originating errors should
// interrupt deletion, so it's okay
function commitUnmount(current: Fiber): void {
onCommitUnmount(current);
switch (current.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
const updateQueue: FunctionComponentUpdateQueue | null = (current.updateQueue: any);
if (updateQueue !== null) {
const lastEffect = updateQueue.lastEffect;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
const destroy = effect.destroy;
if (destroy !== null) {
safelyCallDestroy(current, destroy);
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
break;
}
case ClassComponent: {
// 这里是卸载 ref 的操作
// 因为Ref是可以作用在classcomponent上面的
// classcomponent,具有instance而不像function component 没有 instance
safelyDetachRef(current);
const instance = current.stateNode;
// 然后需要调用它的 componentWillUnmount 这个方法
if (typeof instance.componentWillUnmount === 'function') {
safelyCallComponentWillUnmount(current, instance);
}
return;
}
case HostComponent: {
safelyDetachRef(current); // 只卸载 ref
return;
}
case HostPortal: {
// TODO: this is recursive.
// We are also not using this parent because
// the portal will get pushed immediately.
if (supportsMutation) {
unmountHostComponents(current); // 注意,这里走了一个递归,也就是调用上级函数,对应上面的 commitNestedUnmounts
} else if (supportsPersistence) {
emptyPortalContainer(current);
}
return;
}
}
}
commitNestedUnmounts
// 要调用这个方法,说明我们遇到了一个 HostComponent 节点或 HostText 节点,主要是针对 HostComponent
function commitNestedUnmounts(root: Fiber): void {
// While we're inside a removed host node we don't want to call
// removeChild on the inner nodes because they're removed by the top
// call anyway. We also want to call componentWillUnmount on all
// composites before this host node is removed from the tree. Therefore
// we do an inner loop while we're still inside the host node.
let node: Fiber = root;
// 一进来就是一个 while true 循环,对每一个节点执行 commitUnmount
// 在这个过程中如果找到了有 HostPortal,也对它执行这个方法
// 它又会去调用我们刚才的那个方法,这就是一个嵌套的递归调用的一个过程
// 最终目的是要把整个子树给它遍历完成
while (true) {
commitUnmount(node); // 注意这里,一进来就执行这个,这个方法就是上面的那个方法
// Visit children because they may contain more composite or host nodes.
// Skip portals because commitUnmount() currently visits them recursively.
if (
node.child !== null &&
// If we use mutation we drill down into portals using commitUnmount above.
// If we don't use mutation we drill down into portals here instead.
(!supportsMutation || node.tag !== HostPortal)
) {
node.child.return = node;
node = node.child;
continue;
}
if (node === root) {
return;
}
// node 一定是 root 节点的子树, 向上找含有兄弟节点的节点
while (node.sibling === null) {
if (node.return === null || node.return === root) {
return;
}
node = node.return;
}
// 找它的 sibling 兄弟节点,继续执行 while 循环
node.sibling.return = node.return;
node = node.sibling;
}
}
commitDeletion
描述了整个删除的流程
最重要的就是理解这个算法它如何进行递归的调用来遍历整棵子树每一个节点的过程
对于 Portal,ClassComponent,还有 HostComponent,会有不同的操作
需要注意的是,对于HostComponent的子树的遍历会放到这个 commitNestedUnmounts
方法里面去做
对于这个 unmountHostComponents
方法,它遍历的过程的目的是
commitNestedUnmounts
方法对于这个整体流程,用下面的图来看下,比如说,要删除图中 App下的 div 节点
第一种场景
commitUnmount
方法commitUnmount
这个方法if ( node.child !== null && (!supportsMutation || node.tag !== HostPortal) )
这个条件,并 continuecommitUnmount
这个方法, 这时候,node.child === null, node !== root, 于是会执行while (node.sibling === null) {
if (node.return === null || node.return === root) {
return;
}
node = node.return;
}
// 找它的 sibling 兄弟节点,继续执行 while 循环
node.sibling.return = node.return;
node = node.sibling;
while (node.sibling === null)
while (node.sibling === null)
,跳出commitUnmount
方法,继续执行if ( node.child !== null && (!supportsMutation || node.tag !== HostPortal) )
这里commitUnmount
方法第二种场景
commitUnmount
方法,进入其内部HostPortal
,调用了 unmountHostComponents
方法,并进入其内部HostPortal
,存在child, 找到其child, 也就是 div 节点,继续内部循环HostComponent
, 需要调用 commitNestedUnmounts
, 这个div只有一个节点,执行完成后removeChildFromContainer
方法,因为对于 Portal来说,currentParentIsContainer
是 trueif (node.return === null || node.return === root)
,它的 return 是root,由此返回结束循环commitUnmount
里面, 看到 case HostPortal 最后是return, 也就是这个方法结束了commitNestedUnmounts
的 while true 里面的 commitUnmount
下面的代码继续执行,会跳过2个if