在上一节 更新props 中,我们判断如果新旧dom的对应tag如果是一样的,name表示更新逻辑,如果不一样,则表示新建或删除。

可以从一个例子中去理解

let show = true
const Counter = () => {
    function handleClick() {
        show = !show
        React.update()
    }

    const Foo = () => <div>foo</div>
    const Bar = () => <p>bar</p>

    return (
        <div>
            count:
            <div>{show ? <Foo></Foo> : <Bar></Bar>}</div>
            <button onClick={handleClick}>click</button>
        </div>
    )
}

上面例子中对应的链表图示如下。

从新旧链表中可以知道,需要将旧的<Foo />删掉,并新建一个<Bar />

Untitled

实现思路:

  1. 先将需要删除的dom(也就是新旧节点不一样的旧dom)收集起来。
  2. 在dom的挂载之前将需要删除的dom删掉,然后执行挂载操作。

1.收集需要删除的dom

let deletions = []  // 新建一个全局变量deletions,将需要删除的节点收集起来
function initChildren(fiber, children) {
    let oldFiber = fiber.alternate?.child;
    let prevChild = null;
    children.forEach((child, index) => {
				// 判断旧的tag与新tag是否相同
				// true:更新节点
				// false:创建、删除节点
        const isSameTag = oldFiber && oldFiber.type === child.type;
        let newFiber = null;
        if (isSameTag) {
            // 更新
            // ...
        } else {
            // 新增
						[// 注:edge case](<https://fortune-dungeon-43c.notion.site/edge-case-27343561d2194845a5ddd4a82c1686d8>)
						if (child) {
                newFiber = {
                    type: child.type,
                    props: child.props,
                    child: null,
                    sibling: null,
                    parent: fiber,
                    dom: null,
                    effectTag: "placement",
                };
            }

						// 删除逻辑,将需要删除的节点收集起来
            if (oldFiber) {
                deletions.push(oldFiber);
            }
        }

        // ...
    });

		// 如果旧的节点的字节点多余新节点的子节点,则需要把纠结点的子节点全部删除
		// 所以循环调用,将sibling全部添加到deletions中
    while (oldFiber) {
        deletions.push(oldFiber);
        oldFiber = oldFiber.sibling;
    }
}

2.挂载之前删除dom

dom挂载环节是在链表创建完成后的 commitRoot() 函数中执行的,所以在挂载之前执行删除dom操作。

function commitRoot() {
    deletions.forEach(commitDeletion); // 执行删除操作
    commitWork(wipRoot.child);
    currentRoot = wipRoot;
    wipRoot = null;
    deletions = []; // 删除完后清空
}
function commitDeletion(fiber) {
		// step1.直接删除节点
    // 问题:如果是函数组件的话,函数组件的dom是null
    // 解决:递归查找子节点
    // fiber.parent.dom.removeChild(fiber.dom)

    // step2.递归查找子节点
    // 问题:函数组件查找到子节点后,因为函数组件dom为null,执行删除时会发现fiber.parent.dom为null,同样有问题
    // 解决:循环查找dom不为null的父节点
    // if (fiber.dom) {
    //     fiber.parent.dom.removeChild(fiber.dom);
    // } else {
    //     commitDeletion(fiber.child);
    // }

    // step3.循环查找dom不为null的父节点
    if (fiber.dom) {
        let fiberParent = fiber.parent;
        while (!fiberParent.dom) {
            fiberParent = fiberParent.parent;
        }
        fiberParent.dom.removeChild(fiber.dom);
    } else {
        commitDeletion(fiber.child);
    }
}