手写 mini-vue3 - 实现更新 Element 的 Props(九)

38 阅读2分钟

这次来到更新 Element 的相关操作了。

更新,即意味着有新的,有旧的,新的替换成旧的。

要找到旧的节点、新的节点的出现位置,然后再次进行 patch,根据 vnode 更新 Element。

获取新旧 vnode

回顾之前的代码,在 setupRenderEffect 的时候,拿到了组件 render 函数返回的 vnode,再次传给 patch,来到 processElement

那我们就可以在 setupRenderEffect 里存储旧节点,获取新节点,传给 patch 这两个新旧节点。

还有一个点是,每当标签内容有变更的时候,就会自动触发更新的逻辑,针对这个,reactiivtyeffect 副作用函数就派上用场了。

// component.ts
createComponentInstance(vnode, parent) {
    const component = {
        isMounted: false,
        subTree: {}
    }

    return component;
}
// renderer.ts
function setupRenderEffect(instance, initialVNode, container) {
    effect(() => {
        if(!instance.isMounted) {
            // subTree 当前实例的 subTree
            const subTree = instance.subTree = instance.render.call(proxy);
            patch(null, subTree, container, instance);
            instance.isMounted = true;
        } else {
            // 获得新的 subTree
            subTree = instance.render.call(proxy);
            // 获得旧的 subTree
            prevSubTree = instance.subTree;
            // 更新组件实例的 subTree 
            instance.subTree = subTree;
            patch(prevSubTree, subTree, container, instance);
        }
    })
}

修改 patch 参数

patch 多加了一个节点参数

// renderer.ts
function render(vnode, container) {
    patch(null, vnode, container, null);
}

function patch(n1, n2, container, parentComponent) {
     processFragment(n1, n2, ...)
     processText(n1, n2, ...)
     processElement(n1, n2, ...)
     processComponent(n1, n2, ...)
}

新增 patchElement 函数

  1. processElement 这里,判断有旧节点存在,就意味着需要更新,没有旧节点,就意味着初始化渲染

    // renderer.ts
    function processElement(n1, n2, container, parentComponent) {
        if(!n1) {
            mountElement(n2, container, parentComponent)
        } else {
            patchElement(n1, n2, container);
        }
    }
    
    function patchElement(n1, n2, container) {
        // todo
    }
    

proxyRefs 处理 setup 执行结果

  1. handleSetupResult 执行 setup 函数得到的返回结果,使用 proxyRefs 内部的 unRef 功能,使 render 函数内部访问 ref 对象时,省去 .value 的访问。
    function handleSetupResult(instance, setupResult) {
        // 如果 setup 返回的是 object
        if(typeof setupResult === 'object') {
            // 设置组件实例的 setupState 状态为 setup 返回的这个对象
            instance.setupState = proxyRefs(setupResult);
        }
        // 完成组件 setup
        finishComponentSetup(instance);
    }
    

更新 Element 的 Props

// renderer.ts 
function createRenderer({ patchProp: hostPatchProp }) {
    const EMPTY_OBJ = {};
    function patchElement(n1, n2, container) {
        // 获得新旧 props
       oldProps = n1.props || EMPTY_OBJ;
       newProps = n2.props || EMPTY_OBJ;

       // 在这里更新 el
       el = (n2.el = n1.el);
       patchProps(el, oldProps, newProps);
    }

    function patchProps(el, oldProps, newProps) {
        // 添加|更新 props
        for (key in newProps) {
            prevProp = oldProps[key];
            nextProp = newProps[key];

            // 对比,是不同的值
            if (prevProp !== nextProp) {
                // 更新 props
                hostPatchProp(el, key, prevProp, nextProp);
            }
        }

        // 移除不存在的旧 props
        if (oldProps !== EMPTY_OBJ) {
            for (key in oldProps) {
                // 新的 props 没有找到旧的 props
                if !(key in newProps) {
                    // 移除旧的 props
                    hostPatchProp(el, key, oldProps[key], null);
                }
            }
        }
    }
}
// runtime-dom/index.ts
patchProp(el, key, prevVal, nextVal) {
    if (isOn(key)) {
        el.addEventListener(event, nextVal);
    } else {
        if (nextVal == null) {
            el.removeAttribute(key);
        } else {
            el.setAttribute(key, nextVal);
        }
    }
}