Vue2-指令器

153 阅读2分钟

directive包含5个钩子函数

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  • unbind:只调用一次,指令与元素解绑时调用。

首先会将用户传入的指令转化为下面的形式

 let app = new Vue({
     el : '#box',
     directives : {
         test : {
             bind(){
                 console.log('bind')
             },
             inserted(){
                 console.log('insert')
             },
             update(){
                 console.log('update')
             },
             componentUpdated(){
                 console.log('componentUpdated')
             },
             unbind(){
                 console.log('unbind')
             }
         }
     }
 })

先转化一层

 directives : [{name: 'test', rawName: 'v-test'}]

第二层

 {
     v-test: {
         def: {bind: ƒ, inserted: ƒ, update: ƒ, componentUpdated: ƒ, unbind: ƒ}
          modifiers: {}
          name: "test"
          rawName: "v-test"
     }
 }
 

bind,update,unbind都是直接触发的,代码如下

 for (key in newDirs) {
     oldDir = oldDirs[key]
     dir = newDirs[key]
     if (!oldDir) {  //判断旧指令不存在 就触发bind函数
       // new directive, bind
       callHook(dir, 'bind', vnode, oldVnode) // 直接执行bind函数
       if (dir.def && dir.def.inserted) {
         dirsWithInsert.push(dir)
       }
     } else {    // 更新了节点
       // existing directive, update
       dir.oldValue = oldDir.value
       dir.oldArg = oldDir.arg
       callHook(dir, 'update', vnode, oldVnode)  // 旧指令和新指令都有 直接执行update函数
       if (dir.def && dir.def.componentUpdated) {
         dirsWithPostpatch.push(dir)
       }
     }
   }
   
   
   if (!isCreate) {
     for (key in oldDirs) {
       if (!newDirs[key]) {  //拿旧指令对比新指令
         // no longer present, unbind
         callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy) //如果旧的指令在新指令中不存在就卸载,顺便执行unbind函数
       }
     }
   }

接下来来看insert处理和componentUpdated处理

首先是insert

1.先保存钩子函数

 if (dir.def && dir.def.inserted) {
     dirsWithInsert.push(dir)
 }

2.组装成函数callInsert

3.合并到insert钩子

 if (dirsWithInsert.length) {
     const callInsert = () => {
       for (let i = 0; i < dirsWithInsert.length; i++) {
         callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode)
       }
     }
     if (isCreate) {
       mergeVNodeHook(vnode, 'insert', callInsert)
     } else {
       callInsert()
     }
 }

保存之后,那么就需要执行

1.第一次创建的时候,会去调用mergeVNodeHook函数来将insert钩子函数合并到vnode身上,因为此时节点还没有插入到dom中

 mergeVNodeHook(vnode, 'insert', callInsert)
 ​
 export function mergeVNodeHook (def: Object, hookKey: string, hook: Function) {
   if (def instanceof VNode) {
     def = def.data.hook || (def.data.hook = {})
   }
   let invoker
   const oldHook = def[hookKey]
 ​
   function wrappedHook () {
     hook.apply(this, arguments)
     // important: remove merged hook to ensure it's called only once
     // and prevent memory leak
     remove(invoker.fns, wrappedHook)
   }
 ​
   if (isUndef(oldHook)) {
     // no existing hook
     invoker = createFnInvoker([wrappedHook])
   } else {
     /* istanbul ignore if */
     if (isDef(oldHook.fns) && isTrue(oldHook.merged)) {
       // already a merged invoker
       invoker = oldHook
       invoker.fns.push(wrappedHook)
     } else {
       // existing plain hook
       invoker = createFnInvoker([oldHook, wrappedHook])
     }
   }
 ​
   invoker.merged = true
   def[hookKey] = invoker
 }
 ​
 export function createFnInvoker (fns: Function | Array<Function>, vm: ?Component): Function {
   function invoker () {
     const fns = invoker.fns
     if (Array.isArray(fns)) {
       const cloned = fns.slice()
       for (let i = 0; i < cloned.length; i++) {
         invokeWithErrorHandling(cloned[i], null, arguments, vm, `v-on handler`)
       }
     } else {
       // return handler return value for single handlers
       return invokeWithErrorHandling(fns, null, arguments, vm, `v-on handler`)
     }
   }
   invoker.fns = fns
   return invoker
 }

2.绑定之后就要去执行了 执行的顺序是 updateComponent -> patch -> invokeInsertHook

 function invokeInsertHook (vnode, queue, initial) {
     // delay insert hooks for component root nodes, invoke them after the
     // element is really inserted
     if (isTrue(initial) && isDef(vnode.parent)) {
       vnode.parent.data.pendingInsert = queue
     } else {
       for (let i = 0; i < queue.length; ++i) {
         queue[i].data.hook.insert(queue[i])
       }
     }
 }

依次遍历里面的insert钩子函数并且执行

其次是componentUpdated执行

1.先保存钩子函数

 if (dir.def && dir.def.componentUpdated) {
     dirsWithPostpatch.push(dir)
 }

2.更本节点的postpatch合并起来

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

3.在更新节点的时候就立马执行

 if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)