vueカスタム命令原理

21818 ワード

vue命令の本質
命令は本質的に装飾器であり、vueによるHTML要素の拡張であり、HTML要素にカスタム機能を追加し、HTMLラベルを意味化する.vueがDOMをコンパイルすると,命令に関連付けられたJSコード,すなわち命令オブジェクトを見つけ,命令オブジェクトに関するメソッドを実行する.
カスタム命令ライフサイクル
カスタム命令には、bind、inserted、update、componentUpdated、unbindの5つのライフサイクル(フック関数とも呼ばれる)があります.
フック関数の役割の紹介
  • bind:一度だけ呼び出し、命令が初めて要素にバインドされたときに呼び出され、このフック関数でバインド時に一度の初期化動作を定義できます.
  • inserted:バインド要素が親ノードに挿入されたときに呼び出されます(親ノードは存在し、documentに保存する必要はありません).
  • update:バインド値が変化するかどうかにかかわらず、要素が存在するテンプレートの更新にバインドされたときに呼び出されます.更新前後のバインド値を比較することで、不要なテンプレート更新を無視できます.
  • componentUpdated:バインドされた要素があるテンプレートが更新サイクルを完了したときに呼び出されます.
  • unbind:1回のみ呼び出され、命令が要素にバインドされていないときに呼び出されます.

  • インプリメンテーションプロセス
    ソースコード
    //   2.6.10
    export default {
      create: updateDirectives,
      update: updateDirectives,
      destroy: function unbindDirectives (vnode: VNodeWithData) {
        updateDirectives(vnode, emptyNode)
      }
    }
    
    function updateDirectives (oldVnode: VNodeWithData, vnode: VNodeWithData) {
      if (oldVnode.data.directives || vnode.data.directives) {
        _update(oldVnode, vnode)
      }
    }
    
    function _update (oldVnode, vnode) {
      const isCreate = oldVnode === emptyNode //                  
      const isDestroy = vnode === emptyNode //           ,         , true
      const oldDirs = normalizeDirectives(oldVnode.data.directives, oldVnode.context) //      
      const newDirs = normalizeDirectives(vnode.data.directives, vnode.context) //      
    
      const dirsWithInsert = [] //       inserted       
      const dirsWithPostpatch = [] //       componentUpdated       
    
      let key, oldDir, dir
      for (key in newDirs) {
        oldDir = oldDirs[key]
        dir = newDirs[key]
        if (!oldDir) { //   oldDir    ,     ,         
          //   bind
          callHook(dir, 'bind', vnode, oldVnode)
          //        inserted  ,     dirsWithInsert,        bind     inserted  
          if (dir.def && dir.def.inserted) {
            dirsWithInsert.push(dir)
          }
        } else {
          // oldDir  ,     
          dir.oldValue = oldDir.value
          dir.oldArg = oldDir.arg
          callHook(dir, 'update', vnode, oldVnode)
          //        componentUpdated  ,     dirsWithPostpatch,
          //        vnode  vnode    (      update   ),  componentUpdated  
          if (dir.def && dir.def.componentUpdated) {
            dirsWithPostpatch.push(dir)
          }
        }
      }
    
      if (dirsWithInsert.length) {
        const callInsert = () => {
          for (let i = 0; i < dirsWithInsert.length; i++) {
            callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode)
          }
        }
        if (isCreate) {
          //          ,  mergeVNodeHook                        
          //                              
          mergeVNodeHook(vnode, 'insert', callInsert)
        } else {
          callInsert()
        }
      }
    
      if (dirsWithPostpatch.length) {
        mergeVNodeHook(vnode, 'postpatch', () => {
          for (let i = 0; i < dirsWithPostpatch.length; i++) {
            callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode)
          }
        })
      }
    
      //                
      if (!isCreate) {
        //        ,      ,        ,      unbind  
        for (key in oldDirs) {
          if (!newDirs[key]) {
            callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy)
          }
        }
      }
    }
    
    const emptyModifiers = Object.create(null)
    
    function normalizeDirectives (
      dirs: ?Array<VNodeDirective>,
      vm: Component
    ): { [key: string]: VNodeDirective } {
      const res = Object.create(null)
      if (!dirs) {
        return res
      }
      let i, dir
      for (i = 0; i < dirs.length; i++) {
        dir = dirs[i]
        if (!dir.modifiers) {
          dir.modifiers = emptyModifiers
        }
        res[getRawDirName(dir)] = dir
        dir.def = resolveAsset(vm.$options, 'directives', dir.name, true)
      }
      return res
    }
    
    function getRawDirName (dir: VNodeDirective): string {
      return dir.rawName || `${dir.name}.${Object.keys(dir.modifiers || {}).join('.')}`
    }
    
    function callHook (dir, hook, vnode, oldVnode, isDestroy) {
      const fn = dir.def && dir.def[hook]
      if (fn) {
        try {
          fn(vnode.elm, dir, vnode, oldVnode, isDestroy)
        } catch (e) {
          handleError(e, vnode.context, `directive ${dir.name} ${hook} hook`)
        }
      }
    }