DiffDOM原理和Create实现

大家好,我是万维读客的讲师曹欢欢。上节我们拆分了render和commit阶段,这里我们开始处理render中的DOM Diff操作,区分不同状态。

DiffDOM原理

DOM树的diff算法主要是用来处理DOM更新时的操作,只需要差异化更新变更的DOM节点即可,而不需要重新渲染整个DOM树。

DOM diff算法有个约定,通过约定可以将diff算法的复杂度降低到O(N),一次遍历即可解决。

- DOM节点跨级移动较少,可以忽略不计
- 相同类型的组件会产生类似的结构,不同类型的组件产生的结构不同
- 同一级别的节点可以通过key区分不同

实现DOMDiff

上节我们拆分的render阶段,增加了函数reconcile, DOM的处理主要在这个函数里,我们需要区分出来DOM的mount、update、delete三种情况。修改代码如下:

function reconcile(fiber) {
    // 用链表处理child
    let preSibling = null;
    // dom diff: mount/update/delete
    let oldFiber = fiber.alternate?.child;
    // fiber.props?.children.forEach((child, idx) => {
    let idx = 0;
    while (idx < fiber.props?.children.length || oldFiber) {
        const child = fiber.props?.children[idx];
        let newFiber = null;
        let isSameType = oldFiber && child && oldFiber.type == child.type;
        if (child && (!oldFiber || !isSameType)) {
            // mount
            newFiber = {
                type: child.type,
                stateNode: null,
                props: child.props,
                return: fiber,
                alternate: null,
                child: null,
                sibling: null,
                effectTag: 'PLACEMENT'
            }
        } else if (isSameType && oldFiber) {
            // update
            newFiber = {
                type: child.type,
                stateNode: oldFiber.stateNode,
                props: child.props,
                return: fiber,
                alternate: oldFiber,
                child: null,
                sibling: null,
                effectTag: 'UPDATE'
            }
        } else if (!isSameType && oldFiber) {
            // delete 
        }

        if (idx == 0) {
            fiber.child = newFiber;
        } else {
            preSibling.sibling = newFiber;
        }
        if (oldFiber) {
            oldFiber = oldFiber.sibling;
        }
        preSibling = newFiber;

        idx++;
    }
}

修改commit

在reconcile处理阶段我们给不同的fiber打上了不同的effectTag标签,这里我们对不同的类型,进行不同的commit操作。

function commitDOM(fiber) {
    let tempParenNode = null;
    if (fiber.return && fiber.stateNode) {
        tempParenNode = fiber.return;
        while (!tempParenNode.stateNode) {
            tempParenNode = tempParenNode.return;
        }
    }
    // mount情况
    if (tempParenNode && fiber.effectTag === 'PLACEMENT') {
        // 属性处理,来自于上一节reconcile中
        Object.keys(fiber.props).filter(filerProps).forEach(key => {
            fiber.stateNode[key] = fiber.props[key];
        })
        Object.keys(fiber.props).filter(isEvent).forEach(key => {
            const eventName = key.toLowerCase().substring(2);
            fiber.stateNode.addEventListener(eventName, fiber.props[key]);
        })
        tempParenNode.stateNode.appendChild(fiber.stateNode);
    }

    if (fiber.child) {
        commitDOM(fiber.child);
    }
    if (fiber.sibling) {
        commitDOM(fiber.sibling);
    }
}

然后查看我们的测试用例,正常通过,说明新增的DOM处理是没问题的。

参考

  1. DOM DIFF文档:https://zhuanlan.zhihu.com/p/362539108


请遵守《互联网环境法规》文明发言,欢迎讨论问题
扫码反馈

扫一扫,反馈当前页面

咨询反馈
扫码关注
返回顶部