2

文章内容

用具体的例子讲解patchVNode的具体流程,通过例子逐渐分析出patchVNode代码所代表的含义

前置知识

由上一篇文章Vue2源码-整体流程浅析可以知道,当两个VNode是同一个VNode时,会触发patchVNode()的执行

prepatch()触发

function patchVnode(oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) {
    //...
    if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
        i(oldVnode, vnode)
    }
    //...
}

从下面代码可以知道,prepatch()主要是调用了updateChildComponent(),这个方法的作用是将newVnode相关数据更新到旧的oldVnode.componentOptions

// vue/src/core/vdom/create-component.js
prepatch(oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
    const options = vnode.componentOptions
    const child = vnode.componentInstance = oldVnode.componentInstance
    updateChildComponent(
        child,
        options.propsData, // updated props
        options.listeners, // updated listeners
        vnode, // new parent vnode
        options.children // new children
    )
}
// vue/src/core/instance/lifecycle.js
export function updateChildComponent(
    vm: Component, // 旧的Component对象
    propsData: ?Object, // 新的props数据
    listeners: ?Object, // 新的listeners数据
    parentVnode: MountedComponentVNode, // 新的VNode数据
    renderChildren: ?Array<VNode> // 新的children数据
) {
    vm.$options._parentVnode = parentVnode
    vm.$vnode = parentVnode // update vm's placeholder node without re-render

    if (vm._vnode) { // update child tree's parent
        vm._vnode.parent = parentVnode
    }
    vm.$options._renderChildren = renderChildren
    vm.$attrs = parentVnode.data.attrs || emptyObject
    vm.$listeners = listeners || emptyObject

    if (propsData && vm.$options.props) {
        toggleObserving(false)
        const props = vm._props
        const propKeys = vm.$options._propKeys || []
        for (let i = 0; i < propKeys.length; i++) {
            const key = propKeys[i]
            const propOptions: any = vm.$options.props // wtf flow?
            props[key] = validateProp(key, propOptions, propsData, vm)
        }
        toggleObserving(true)
        vm.$options.propsData = propsData
    }
    //...省略代码,基本跟上面代码逻辑一致,进行vm的更新而已
}

触发update()方法执行

function patchVnode(oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) {
    //...
    if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
        i(oldVnode, vnode)
    }


    if (isDef(data) && isPatchable(vnode)) {
        for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
        if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
    }

    //...
}
参考Vue2自定义指令的钩子函数:https://v2.cn.vuejs.org/v2/guide/custom-directive.html#ad

从文档可以知道,update()的钩子函数,代表所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
比如下面的示例代码,如果我们注册了一个test33的自定义指令,那么<div v-test33></div>所在组件的 VNode 更新时调用注册的update()方法

// html
<template>
    <div v-test33></div>
</template>

// js
Vue.directive('test33', {
    update: function (el) {
        console.error("测试update 自定义指令");
    }
})

分情况进行children的比较

function patchVnode(oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) {
    //...
    if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
        i(oldVnode, vnode)
    }
    const oldCh = oldVnode.children
    const ch = vnode.children
    //...

    if (isUndef(vnode.text)) {
        if (isDef(oldCh) && isDef(ch)) {
            if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
        } else if (isDef(ch)) {
            if (process.env.NODE_ENV !== 'production') {
                checkDuplicateKeys(ch)
            }
            if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
            addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
        } else if (isDef(oldCh)) {
            removeVnodes(oldCh, 0, oldCh.length - 1)
        } else if (isDef(oldVnode.text)) {
            nodeOps.setTextContent(elm, '')
        }
    } else if (oldVnode.text !== vnode.text) {
        nodeOps.setTextContent(elm, vnode.text)
    }
}

从上面的代码可以知道,当prepatch()newVNode的相关数据更新到oldVNode.componentOptions后,会进行它们children的比较,确定它们children的更新策略,总结下来为

  • oldVNodeChildren!==newVNodeChildren,触发updateChildren()方法
  • oldVNodeChildren为空,newVNodeChildren不为空,执行newVNodeChildren新建插入操作
  • oldVNodeChildren不为空,newVNodeChildren为空,执行oldVNodeChildren删除操作
  • 如果是文本节点,则更新文本内容

核心部分updateChildren()

前置说明

  • 由于流程过于复杂,将使用一个具体的例子说明源码的流程
  • patchVnode(oldVnode, newVnode, insertedVnodeQueue, newCh, newIdx):当oldVnode=newVnode的时候,才会触发patchVnode()方法,进行新旧VNodeData的更新,然后进行children元素的比较,进行新增删除/向下比较触发updateChildren(),除了更新数据(同个VNode进行数据更新)、处理children(addVNode/removeVNode/updateVNode-选择性更新),不做其它处理

    下面所有图中旧的children真实情况下应该存在oldVNode的DOM引用情况,为了偷懒,会将矩形外边的节点当作oldVNode引用DOM的位置,矩形包裹才是旧VNode的位置,因此会出现情况5中就算移动了元素到其它位置,仍然存在元素在oldVNode中的情况

条件分类代码

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    if (sameVnode(oldStartVnode, newStartVnode)) {
        // 情况1: oldStartIdx === newStartIdx
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
        // 情况2: oldEndIdx === newEndIdx
    } else if (sameVnode(oldStartVnode, newEndVnode)) { 
        // 情况3: oldStartIdx === newEndIdx
    } else if (sameVnode(oldEndVnode, newStartVnode)) { 
        // 情况4: oldEndIdx === newStartIdx
    } else {
        // 情况5: 四端都找不到同样的VNode
    }
}

情况4: oldEndIdx===newStartIdx

oldEndVNode==newStartVNode.svg

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    if (sameVnode(oldStartVnode, newStartVnode)) {
        // 情况1: oldStartIdx === newStartIdx
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
        // 情况2: oldEndIdx === newEndIdx
    } else if (sameVnode(oldStartVnode, newEndVnode)) {
        // 情况3: oldStartIdx === newEndIdx
    } else if (sameVnode(oldEndVnode, newStartVnode)) {
        // 情况4: oldEndIdx === newStartIdx

        // 数据更新,如果有children,也在patchVnode()内部进行调用处理,addVNode/removeVNode/updateChildren
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
        // 调整目前DOM元素的位置,将oldEndVnode的DOM移动到oldStartVnode的DOM前面
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx] // 更新索引
        newStartVnode = newCh[++newStartIdx] // 更新索引
    } else {
        // 情况5: 四端都找不到同样的VNode
    }
}

情况2: oldEndIdx===newEndIdx

oldEndIdx==newEndIdx.svg

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    if (sameVnode(oldStartVnode, newStartVnode)) {
        // 情况1: oldStartIdx === newStartIdx
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
        // 情况2: oldEndIdx === newEndIdx

        // 数据更新,如果有children,也在patchVnode()内部进行调用处理,addVNode/removeVNode/updateChildren
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]
    } else if (sameVnode(oldStartVnode, newEndVnode)) {
        // 情况3: oldStartIdx === newEndIdx
    } else if (sameVnode(oldEndVnode, newStartVnode)) {
        // 情况4: oldEndIdx === newStartIdx

        //......
    } else {
        // 情况5: 四端都找不到同样的VNode
    }
}

情况3: oldStartIdx === newEndIdx

oldStartIdx==newEndIdx.svg

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    if (sameVnode(oldStartVnode, newStartVnode)) {
        // 情况1: oldStartIdx === newStartIdx
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
        // 情况2: oldEndIdx === newEndIdx

        //....
    } else if (sameVnode(oldStartVnode, newEndVnode)) {
        // 情况3: oldStartIdx === newEndIdx

        // 数据更新,如果有children,也在patchVnode()内部进行调用处理,addVNode/removeVNode/updateChildren
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
        // 调整目前DOM元素的位置,将oldStartVnode的DOM移动到oldEndVnode的DOM后面
        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
        oldStartVnode = oldCh[++oldStartIdx]
        newEndVnode = newCh[--newEndIdx]
    } else if (sameVnode(oldEndVnode, newStartVnode)) {
        // 情况4: oldEndIdx === newStartIdx

        //......
    } else {
        // 情况5: 四端都找不到同样的VNode
    }
}

情况1: oldStartIdx === newStartIdx

oldStartIdx==newStartIdx.svg

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    if (sameVnode(oldStartVnode, newStartVnode)) {
        // 情况1: oldStartIdx === newStartIdx

        // 数据更新,如果有children,也在patchVnode()内部进行调用处理,addVNode/removeVNode/updateChildren
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
        // 情况2: oldEndIdx === newEndIdx

        //....
    } else if (sameVnode(oldStartVnode, newEndVnode)) {
        // 情况3: oldStartIdx === newEndIdx

        //....
    } else if (sameVnode(oldEndVnode, newStartVnode)) {
        // 情况4: oldEndIdx === newStartIdx

        //......
    } else {
        // 情况5: 四端都找不到同样的VNode
    }
}

情况5: 四端都无法找到可以复用,相同的VNode

四端无法找到可匹配的VNode,但是中间可以找到

oldKeyToIndx_sameVnode.svg

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    if (sameVnode(oldStartVnode, newStartVnode)) {
        // 情况1: oldStartIdx === newStartIdx
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
        // 情况2: oldEndIdx === newEndIdx
    } else if (sameVnode(oldStartVnode, newEndVnode)) {
        // 情况3: oldStartIdx === newEndIdx
    } else if (sameVnode(oldEndVnode, newStartVnode)) {
        // 情况4: oldEndIdx === newStartIdx
    } else {
        // 情况5: 四端都找不到同样的VNode
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        idxInOld = isDef(newStartVnode.key)
            ? oldKeyToIdx[newStartVnode.key]
            : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
        if (isUndef(idxInOld)) { // New element
            // ... 
        } else {
            // 命中下面代码
            vnodeToMove = oldCh[idxInOld]
            if (sameVnode(vnodeToMove, newStartVnode)) {
                // 数据更新,如果有children,也在patchVnode()内部进行调用处理,addVNode/removeVNode/updateChildren
                patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
                // 将索引中的oldCh[idxInOld]置为空,方便后面直接跳过
                oldCh[idxInOld] = undefined
                // 调整目前DOM元素的位置,将oldCh[idxInOld]的DOM移动到oldStartVnode前面
                canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
            } else {
                // same key but different element. treat as new element
                createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
            }
        }
        newStartVnode = newCh[++newStartIdx]
    }
}
(新条件1)oldStartVnode=undefine:中间区域可复用区域直接跳过下一次循环

oldStartVNode为空.svg

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    if (isUndef(oldStartVnode)) {
        // 新条件1:oldStartVnode=undefine
        oldStartVnode = oldCh[++oldStartIdx] // 情况5置为undefined的startVNode
    } else if (sameVnode(oldStartVnode, newStartVnode)) {
        // 情况1: oldStartIdx === newStartIdx
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
        // 情况2: oldEndIdx === newEndIdx
    } else if (sameVnode(oldStartVnode, newEndVnode)) {
        // 情况3: oldStartIdx === newEndIdx
    } else if (sameVnode(oldEndVnode, newStartVnode)) {
        // 情况4: oldEndIdx === newStartIdx
    } else {
        // 情况5: 四端都找不到同样的VNode
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        idxInOld = isDef(newStartVnode.key)
            ? oldKeyToIdx[newStartVnode.key]
            : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
        if (isUndef(idxInOld)) { // New element
            // ... 
        } else {
            // 命中下面代码
            vnodeToMove = oldCh[idxInOld]
            if (sameVnode(vnodeToMove, newStartVnode)) {
                // ...
                oldCh[idxInOld] = undefined
                // ...
            } else {
                // ...
            }
        }
        newStartVnode = newCh[++newStartIdx]
    }
}
四端无法找到可匹配的VNode,中间也无法找到,属于新增元素

newStartIdx大于newEndIdx.svg

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    if (isUndef(oldStartVnode)) {
        oldStartVnode = oldCh[++oldStartIdx] // 情况5置为undefined的startVNode
    } else if (sameVnode(oldStartVnode, newStartVnode)) {
        // 情况1: oldStartIdx === newStartIdx
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
        // 情况2: oldEndIdx === newEndIdx
    } else if (sameVnode(oldStartVnode, newEndVnode)) {
        // 情况3: oldStartIdx === newEndIdx
    } else if (sameVnode(oldEndVnode, newStartVnode)) {
        // 情况4: oldEndIdx === newStartIdx
    } else {
        // 情况5: 四端都找不到同样的VNode
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        idxInOld = isDef(newStartVnode.key)
            ? oldKeyToIdx[newStartVnode.key]
            : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
        if (isUndef(idxInOld)) { 
            // 命中下面代码,新增元素并且插入oldStartVNode对应的DOM前面
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
        } else {
            vnodeToMove = oldCh[idxInOld]
            if (sameVnode(vnodeToMove, newStartVnode)) {
                // ...
                oldCh[idxInOld] = undefined
                // ...
            } else {
                // ...
            }
        }
        newStartVnode = newCh[++newStartIdx]
    }
}

(循环结束1)newStartIdx > newEndIdx

结束第一个循环需要删除不用的旧VNode

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    if (isUndef(oldStartVnode)) {
        // 新条件1:oldStartVnode=undefine
        oldStartVnode = oldCh[++oldStartIdx] // 情况5置为undefined的startVNode
    } else if (sameVnode(oldStartVnode, newStartVnode)) {
        // 情况1: oldStartIdx === newStartIdx
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
        // 情况2: oldEndIdx === newEndIdx
    } else if (sameVnode(oldStartVnode, newEndVnode)) {
        // 情况3: oldStartIdx === newEndIdx
    } else if (sameVnode(oldEndVnode, newStartVnode)) {
        // 情况4: oldEndIdx === newStartIdx
    } else {
        // 情况5: 四端都找不到同样的VNode,中间可复用,移动DOM/中间不可复用,新增插入DOM
        if (isUndef(idxInOld)) {
            // 四端都找不到同样的VNode,中间不可复用,新增插入DOM
        } else {
            vnodeToMove = oldCh[idxInOld]
            if (sameVnode(vnodeToMove, newStartVnode)) {
                // 四端都找不到同样的VNode,中间可复用,移动DOM
            } else {
                // key相同,其它条件不同当作新VNode处理,即中间不可复用,新增插入DOM处理
            }
        }
    }
}
if (newStartIdx > newEndIdx) {
    // 循环结束1:删除已经废弃的旧VNode
    removeVnodes(oldCh, oldStartIdx, oldEndIdx)
}
从上面分析代码可以看出,条件中还缺少oldEndVnode不存在以及结束第一个循环时,oldStartIdx > oldEndIdx两种可能性的判断,事实上这两种情况也有可能发生

(新条件2)oldEndVnode=undefine

暂时还没想好场景,先作为补齐条件判断加入
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    if (isUndef(oldStartVnode)) {
        // 新条件1:oldStartVnode=undefine
        oldStartVnode = oldCh[++oldStartIdx] // 情况5置为undefined的startVNode
    } else if (isUndef(oldEndVnode)) {
        // 新条件2:oldEndVnode=undefine
        oldEndVnode = oldCh[--oldEndIdx]
    } else if (sameVnode(oldStartVnode, newStartVnode)) {
        // 情况1: oldStartIdx === newStartIdx
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
        // 情况2: oldEndIdx === newEndIdx
    } else if (sameVnode(oldStartVnode, newEndVnode)) {
        // 情况3: oldStartIdx === newEndIdx
    } else if (sameVnode(oldEndVnode, newStartVnode)) {
        // 情况4: oldEndIdx === newStartIdx
    } else {
        // 情况5: 四端都找不到同样的VNode,中间可复用,移动DOM/中间不可复用,新增插入DOM
        if (isUndef(idxInOld)) {
            // 四端都找不到同样的VNode,中间不可复用,新增插入DOM
        } else {
            vnodeToMove = oldCh[idxInOld]
            if (sameVnode(vnodeToMove, newStartVnode)) {
                // 四端都找不到同样的VNode,中间可复用,移动DOM
            } else {
                // key相同,其它条件不同当作新VNode处理,即中间不可复用,新增插入DOM处理
            }
        }
    }
}
if (newStartIdx > newEndIdx) {
    // 删除已经废弃的旧VNode
    removeVnodes(oldCh, oldStartIdx, oldEndIdx)
}

(循环结束2)oldStartIdx>oldEndIdx

由上面的分析可以知道,一开始会触发情况1: oldStartIdx === newStartIdx,然后oldStartIdx++oldEndIdx++,此时由于oldStartIdx>oldEndIdx,循环结束
oldStartIdx大于oldEndIdx.svg
从上图可以看出,目前newCh还有一个VNode未处理,因此需要遍历[newStartIdx, newEndIdx]进行剩余元素的添加处理,如下面代码所示,循环区间,进行createElm()操作

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    if (isUndef(oldStartVnode)) {
        // 新条件1:oldStartVnode=undefine
        oldStartVnode = oldCh[++oldStartIdx] // 情况5置为undefined的startVNode
    } else if (isUndef(oldEndVnode)) {
        // 新条件2:oldEndVnode=undefine
        oldEndVnode = oldCh[--oldEndIdx]
    } else if (sameVnode(oldStartVnode, newStartVnode)) {
        // 情况1: oldStartIdx === newStartIdx
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
        // 情况2: oldEndIdx === newEndIdx
    } else if (sameVnode(oldStartVnode, newEndVnode)) {
        // 情况3: oldStartIdx === newEndIdx
    } else if (sameVnode(oldEndVnode, newStartVnode)) {
        // 情况4: oldEndIdx === newStartIdx
    } else {
        // 情况5: 四端都找不到同样的VNode,中间可复用,移动DOM/中间不可复用,新增插入DOM
        if (isUndef(idxInOld)) {
            // 四端都找不到同样的VNode,中间不可复用,新增插入DOM
        } else {
            vnodeToMove = oldCh[idxInOld]
            if (sameVnode(vnodeToMove, newStartVnode)) {
                // 四端都找不到同样的VNode,中间可复用,移动DOM
            } else {
                // key相同,其它条件不同当作新VNode处理,即中间不可复用,新增插入DOM处理
            }
        }
    }
}
if (oldStartIdx > oldEndIdx) {
    // 如果新的children最后一个节点已经处理完成,那么初始化refElm为最后一个节点
    // 由下面insert方法可以知道,插入元素会nodeOps.insertBefore(parent, elm, ref)
    // 不断在ref前面插入[newStartIdx,newEndIdx]的DOM
    // 由于ref一致都不会动,因此[newStartIdx,newEndIdx]可以顺序插入
    // 如果新的children最后一个节点还没处理,那么处理refElm=null
    // 由下面insert方法可以知道,插入元素会nodeOps.appendChild(parent, elm)
    refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
    // addVnodes:[newStartIdx,newEndIdx]遍历调用createElm插入DOM元素
    addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
    // 循环结束1: 删除多余的oldVNode
}

// addVnodes->createElm()->insert()
function insert(parent, elm, ref) {
    if (isDef(parent)) {
        if (isDef(ref)) {
            if (nodeOps.parentNode(ref) === parent) {
                nodeOps.insertBefore(parent, elm, ref)
            }
        } else {
            nodeOps.appendChild(parent, elm)
        }
    }
}

触发postpatch()执行

if (isDef(data)) {
    if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
}
参考Vue2自定义指令的钩子函数:https://v2.cn.vuejs.org/v2/guide/custom-directive.html#ad

从下面的源码分析和文档可以知道,postpatch()实际上调用的是componentUpdated()的钩子函数,代表指令所在组件的 VNode 及其子 VNode 全部更新后调用

if (dirsWithPostpatch.length) {
    mergeVNodeHook(vnode, 'postpatch', function () {
        for (var i = 0; i < dirsWithPostpatch.length; i++) {
            callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode);
        }
    });
}

比如下面的示例代码,如果我们注册了一个test33的自定义指令,那么<div v-test33></div>所在组件的 VNode 及其子 VNode 全部更新后就会调用注册的componentUpdated()方法

// html
<template>
    <div v-test33></div>
</template>

//js
Vue.directive('test33', {
    componentUpdated: function () {
        console.error("测试componentUpdated");
    }
})

参考文章

  1. vue-design

Vue系列其它文章

  1. Vue2源码-响应式原理浅析
  2. Vue2源码-整体流程浅析
  3. Vue2源码-双端比较diff算法 patchVNode流程浅析
  4. Vue3源码-响应式系统-依赖收集和派发更新流程浅析
  5. Vue3源码-响应式系统-Object、Array数据响应式总结
  6. Vue3源码-响应式系统-Set、Map数据响应式总结
  7. Vue3源码-响应式系统-ref、shallow、readonly相关浅析
  8. Vue3源码-整体流程浅析
  9. Vue3源码-diff算法-patchKeyChildren流程浅析

白边
209 声望37 粉丝

源码爱好者,已经完成vue2和vue3的源码解析+webpack5整体流程源码+vite4开发环境核心流程源码+koa2源码