vue.js学习三:vnode的patch

393 阅读3分钟

监听到数据的变化后, 依赖追踪到组件级别的粒度, 组件在内部进行vnode的对比,进行修改, 最后修改挂载修改后的真是DOM

patch可以将vnode渲染成真是的DOM,其实际作用是在现有的真是DOM上进行修改来实现更新视图的,其真实目的不仅是对比vnode之间的差异, 更重要的意义是修改真是DOM。

render函数返回的是vnode, render的参数是createElement, createElement是根据vnode创建真是的DOM。

// vnode.js

export default function (sel, data, children, text, elem) {
    const key = data.key
    return {
        sel,
        data,
        children,
        elem,
        text,
        key
    }
}

********************************************************
// patch.js

import vnode from './vnode'
import createElement from './createElement'
import patchVnode from './patchVnode'
import isSameNode from './isSameNode'

export default function (oldVnode, newVnode) {
    // 判断oldVnode是DOM节点还是虚拟节点
    if (oldVnode.sel == '' || oldVnode.sel == undefined) {
        oldVnode = vnode(oldVnode.tagName.toLowerCase(), {}, [], undefined, oldVnode)
    }
    // 给虚拟节点生成elem指向的真是dom
    let newDom = newVnode.elem
    if (!newVnode.elem) {
        newDom = createElement(newVnode)
    }
    // 比较新老节点是否是同一个节点
    if (isSameNode(oldVnode, newVnode)) {
        patchVnode(oldVnode, newVnode)
    } else {
        console.log('不是同一个节点,进行暴力拆除添加新节点')
        if (oldVnode.elem && oldVnode.elem.parentNode && newDom) {
            oldVnode.elem.parentNode.insertBefore(newDom, oldVnode.elem)
            oldVnode.elem.parentNode.removeChild(oldVnode.elem)
        }
    }
}

**********************************************************
// createElement.js

function createElement (vnode) {
    let domNode = document.createElement(vnode.sel)
    // 判断有子节点还是文本
    if (vnode.text && (!vnode.children || !vnode.children.length)) {
        domNode.innerText = vnode.text
        // pivot.parentNode.insertBefore(domNode, pivot)
    } else if (vnode.children && vnode.children.length) {
        // 递归创建子节点
        for (let i=0; i<vnode.children.length; i++) {
            let childrenDom = createElement(vnode.children[i])
            domNode.append(childrenDom)
        }
    }
    vnode.elem = domNode
    return domNode
}

export default createElement

******************************************************
// patchVnode.js

import updateChildren from './updateChildren'
import createElement from './createElement'


export default function (oldVnode, newVnode) {
    console.log('是同一个节点, 进行精细化比较')
    // 1、判断新旧节点是否是同一个节点
    if (oldVnode === newVnode) {
        console.log('新旧节点是同一个节点')
        return
        // 什么都不做
    }
    // 新节点有text属性
    if (newVnode.text && (!newVnode.children || !newVnode.children.length)) {
        console.log('新节点是text,没有children')
        if (oldVnode.text !== newVnode.text || oldVnode.children) {
            oldVnode.elem.innerText = newVnode.text
        }
    } else {
        console.log('新节点有children')
        // 新节点没有text属性
        // 1、新老节点有childrens属性
        if (oldVnode.children && oldVnode.children.length) {
            console.log('新老节点都有childrens属性', oldVnode.elem, oldVnode, newVnode)
            updateChildren(oldVnode.elem, oldVnode.children, newVnode.children)
        } else {
            console.log('新节点有children 老节点没有')
            oldVnode.elem.innerHTML = ''
            // 2、新节点有children 老节点没有
            for (let i=0; i<newVnode.children.length; i++) {
                let child = newVnode.children[i]
                // const newChildDom = createElement(child)
                const newChildDom = child.elem
                oldVnode.elem.appendChild(newChildDom)
            }
        }
    }
}

*********************************************************
// isSameNode.js

export default function (a, b) {
    return a.key === b.key && a.sel === b.sel
}

*********************************************************
// updateChildren.js

import isSameNode from './isSameNode'
import patchVnode from './patchVnode'
/**
 * 首先进行节点移动是真是节点的移动, 移动后虚拟节点需要做删除操作,因为下次训话依然比较的是虚拟节点
 */
export default function (parentDom, oldCh, newCh) {
    // 旧前
    let oldPreIndex = 0;
    // 旧后
    let oldAftIndex = oldCh.length-1;
    // 新前
    let newPreIndex = 0;
    // 新后
    let newAftIndex = newCh.length-1;
    // 旧前节点
    let oldSNode = oldCh[oldPreIndex];
    // 旧后节点
    let oldENode = oldCh[oldAftIndex];
    // 新前节点
    let newSNode = newCh[newPreIndex];
    // 新后节点
    let newENode = newCh[newAftIndex];

    while (oldPreIndex <= oldAftIndex && newPreIndex <= newAftIndex) {
        // 新前与旧前
        if (!oldSNode || !oldCh[oldPreIndex]) {
            oldSNode = oldCh[++oldPreIndex]
        } else if (!oldENode || !oldCh[oldAftIndex]) {
            oldENode = oldCh[--oldAftIndex]
        } else if (!newSNode || !newCh[newPreIndex]) {
            newSNode = newCh[++newPreIndex]
        } else if (!newENode || !newCh[newAftIndex]) {
            newENode = newCh[--newAftIndex]
        }else if (isSameNode(newSNode, oldSNode)) {
            console.log('①新前与旧前', newSNode.text, oldSNode.text)
            patchVnode(newSNode, oldSNode)
            oldSNode = oldCh[++oldPreIndex]
            newSNode = newCh[++newPreIndex]
            // 新前与旧后
        } else if (isSameNode(newSNode, oldENode)) {
            console.log('②新前与旧后',  newSNode.text, oldENode.text)
            parentDom.insertBefore(oldENode.elem, oldSNode.elem)
            patchVnode(newSNode, oldENode)
            oldENode = oldCh[--oldAftIndex]
            newSNode = newCh[++newPreIndex]
            // 新后与旧前
        } else if (isSameNode(newENode, oldSNode)) {
            console.log('③新后与旧前', newENode.text, oldSNode.text)
            parentDom.insertBefore(oldSNode.elem, oldENode.elem.nextSibling)
            patchVnode(newENode, oldSNode)
            oldSNode = oldCh[++oldPreIndex]
            newENode = newCh[--newAftIndex]
            // 新后与旧后
        } else if (isSameNode(newENode, oldENode)) {
            console.log('④新后与旧后', newENode.text, oldENode.text)
            patchVnode(newENode, oldENode)
            oldENode = oldCh[--oldAftIndex]
            newENode = newCh[--newAftIndex]
        } else {
            console.log('⑤未匹配到相同的情况下')
            let keyMap = {}
            // 未匹配到相同的情况下
            for (let i=oldPreIndex; i<oldAftIndex; i++){
                if (oldCh[i]) {
                    keyMap[oldCh[i].key] = i
                }
            }
            const target = keyMap[newSNode.key]
            // 如果存在则移动
            if (target!==undefined) {
                console.log('⑤未匹配到相同的情况下-存在-移动', newSNode.text)
                patchVnode(oldCh[target], newSNode)
                parentDom.insertBefore(oldCh[target].elem, oldSNode.elem)
                oldCh[target] = undefined;
            } else {
                console.log('⑤未匹配到相同的情况下-不存在-添加', newSNode.text)
                // 如不存在则新增
                parentDom.insertBefore(newSNode.elem, oldSNode.elem)
            }
            newSNode = newCh[++newPreIndex]
        }
    }
    // 循环结束
    // ①如果循环结束,新节点还有剩余直接添加
    if (newPreIndex <= newAftIndex) {
        console.log('⑥循环完毕-新节点剩余--new--add')
        for (let i=newPreIndex; i<=newAftIndex; i++) {
            console.log('add...', newCh[i].text)
            parentDom.insertBefore(newCh[i].elem, oldSNode.elem)
        }
    }
    // ②如果循环结束,旧节点还有剩余直接删除
    if (oldPreIndex <= oldAftIndex) {
        console.log('⑥循环完毕-旧节点剩余--old--delete')
        for (let i=oldPreIndex; i<=oldAftIndex; i++) {
            if (oldCh[i] && oldCh[i].elem) {
                console.log('delete...', oldCh[i].text, 'index:', i)
                parentDom.removeChild(oldCh[i].elem)
            }
        }
    }
}