Vue源码系列——patch处理标签

243 阅读2分钟

processElement处理标签

function processElement(n1, n2, container) {
    if (!n1) {
        mountElement(n2, container);
    }
    else {
        updateElement(n1, n2);
    }
}

mountElement挂载标签

  1. 创建对应type的dom
  2. children是string:el.textContent = text
  3. children是array:对children每一项进行patch
  4. patchProp搞定属性,区分属性和事件,分别进行设置
  5. 插入父元素
function mountElement(vnode, container) {
    const { shapeFlag, props } = vnode;
    const el = (vnode.el = createElement(vnode.type));
    if (shapeFlag & 8) {
        setElementText(el, vnode.children);
    }
    else if (shapeFlag & 16) {
        mountChildren(vnode.children, el);
    }
    if (props) {
        for (const key in props) {
            const nextVal = props[key];
            patchProp(el, key, null, nextVal);
        }
    }
    insert(el, container);
}

createElement函数创建dom

function createElement(type) {
    const element = document.createElement(type);
    return element;
}

设置text

function setElementText(el, text) {
    el.textContent = text;
}

对children每一项进行patch

function mountChildren(children, container) {
    children.forEach((VNodeChild) => {
        patch(null, VNodeChild, container);
    });
}

patchProp

function patchProp(el, key, preValue, nextValue) {
    if (isOn(key)) {
        const invokers = el._vei || (el._vei = {});
        const existingInvoker = invokers[key];
        if (nextValue && existingInvoker) {
            existingInvoker.value = nextValue;
        }
        else {
            const eventName = key.slice(2).toLowerCase();
            if (nextValue) {
                const invoker = (invokers[key] = nextValue);
                el.addEventListener(eventName, invoker);
            }
            else {
                el.removeEventListener(eventName, existingInvoker);
                invokers[key] = undefined;
            }
        }
    }
    else {
        if (nextValue === null || nextValue === "") {
            el.removeAttribute(key);
        }
        else {
            el.setAttribute(key, nextValue);
        }
    }
}

插入父元素

function insert(child, parent, anchor = null) {
    if (anchor) {
        parent.insertBefore(child, anchor);
    }
    else {
        parent.appendChild(child);
    }
}

updateElement更新标签

  1. 更新props属性
  2. 更新children子元素
function updateElement(n1, n2, container) {
    const oldProps = (n1 && n1.props) || {};
    const newProps = n2.props || {};
    const el = (n2.el = n1.el);
    patchProps(el, oldProps, newProps);
    patchChildren(n1, n2, el);
}

patchProps

包含属性和事件

  1. 被改变的key更新
  2. 被删除的key删除
 function patchProps(el, oldProps, newProps) {
    for (const key in newProps) {
        const prevProp = oldProps[key];
        const nextProp = newProps[key];
        if (prevProp !== nextProp) {
            hostPatchProp(el, key, prevProp, nextProp);
        }
    }
    for (const key in oldProps) {
        const prevProp = oldProps[key];
        const nextProp = null;
        if (!(key in newProps)) {
            hostPatchProp(el, key, prevProp, nextProp);
        }
    }
    }

patchChildren

更新children string文本,setElementText直接替换 array数组,需要遍历做对比,也就是diff

function patchChildren(n1, n2, container) {
    const { shapeFlag: prevShapeFlag, children: c1 } = n1;
    const { shapeFlag, children: c2 } = n2;
    if (shapeFlag & 8) {
        if (c2 !== c1) {
            setElementText(container, c2);
        }
    }
    else {
        if (prevShapeFlag & 16) {
            if (shapeFlag & 16) {
                patchKeyedChildren(c1, c2, container);
            }
        }
    }
}

setElementText

function setElementText(el, text) {
    el.textContent = text;
}

patchKeyedChildren

太长了,可以专门找一片讲diff的文章

function patchKeyedChildren(c1, c2, container) {
    let i = 0;
    let e1 = c1.length - 1;
    let e2 = c2.length - 1;
    const isSameVNodeType = (n1, n2) => {
        return n1.type === n2.type && n1.key === n2.key;
    };
    while (i <= e1 && i <= e2) {
        const prevChild = c1[i];
        const nextChild = c2[i];
        if (!isSameVNodeType(prevChild, nextChild)) {
            // console.log("两个 child 不相等(从左往右比对)");
            // console.log(`prevChild:${prevChild}`);
            // console.log(`nextChild:${nextChild}`);
            break;
        }
        // console.log("两个 child 相等,接下来对比着两个 child 节点(从左往右比对)");
        patch(prevChild, nextChild, container);
        i++;
    }
    while (i <= e1 && i <= e2) {
        const prevChild = c1[e1];
        const nextChild = c2[e2];
        if (!isSameVNodeType(prevChild, nextChild)) {
            // console.log("两个 child 不相等(从右往左比对)");
            // console.log(`prevChild:${prevChild}`);
            // console.log(`nextChild:${nextChild}`);
            break;
        }
        // console.log("两个 child 相等,接下来对比着两个 child 节点(从右往左比对)");
        patch(prevChild, nextChild, container);
        e1--;
        e2--;
    }
    if (i > e1 && i <= e2) {
        while (i <= e2) {
            // console.log(`需要新创建一个 vnode: ${c2[i].key}`);
            patch(null, c2[i], container);
            i++;
        }
    }
    else if (i > e2 && i <= e1) {
        while (i <= e1) {
            // console.log(`需要删除当前的 vnode: ${c1[i].key}`);
            hostRemove(c1[i].el);
            i++;
        }
    }
    else {
        let s1 = i;
        let s2 = i;
        const keyToNewIndexMap = new Map();
        for (let i = s2; i <= e2; i++) {
            const nextChild = c2[i];
            keyToNewIndexMap.set(nextChild.key, i);
        }
        const toBePatched = e2 - s2 + 1;
        const newIndexToOldIndexMap = new Array(toBePatched);
        for (let index = 0; index < newIndexToOldIndexMap.length; index++) {
            newIndexToOldIndexMap[index] = -1;
        }
        for (i = s1; i <= e1; i++) {
            const prevChild = c1[i];
            const newIndex = keyToNewIndexMap.get(prevChild.key);
            newIndexToOldIndexMap[newIndex] = i;
            if (newIndex === undefined) {
                hostRemove(prevChild.el);
            }
            else {
                // console.log("新老节点都存在");
                patch(prevChild, c2[newIndex], container);
            }
        }
        for (i = e2; i >= s2; i--) {
            const nextChild = c2[i];
            if (newIndexToOldIndexMap[i] === -1) {
                patch(null, c2[i], container);
            }
            else {
                const anchor = i + 1 >= e2 + 1 ? null : c2[i + 1];
                hostInsert(nextChild.el, container, anchor && anchor.el);
            }
        }
    }
}