9. 探究Vue的自定義指令

自定義指令的註冊

Vue的全局API: directive、filter和component註冊以下:node

ASSET_TYPES.forEach(function (type) {
      Vue[type] = function (
        id,
        definition
      ) {
        if (!definition) {// 過濾器註冊
          return this.options[type + 's'][id]
        } else {
          /* istanbul ignore if */
          if (type === 'component') {
            validateComponentName(id);
          }
          // 若是是全局註冊組件,經過Vue.extend生成子組件構造函數
          if (type === 'component' && isPlainObject(definition)) {
            definition.name = definition.name || id;
            definition = this.options._base.extend(definition);
          }
          // 註冊或獲取全局指令,此時並未失效,若是definition是函數則默認監聽bind和update事件
          if (type === 'directive' && typeof definition === 'function') {
            definition = { bind: definition, update: definition };
          }
          this.options[type + 's'][id] = definition;
          return definition
        }
      };
    });
}
複製代碼

以模板爲例:express

<div id="app"><input type="text" class="form-control" v-model="keywords" v-focus></div>
複製代碼

自定義指令在編譯階段的處理

在模板編譯階段,處理屬性的過程當中,會把v-model和v-focus這種解析成:bash

[
    {arg: null, end: 72, isDynamicArg: false, modifiers: undefined, name: "model",rawName: "v-model",start: 54, value: "keywords"},
    {arg: null,end: 83,isDynamicArg: false,modifiers: undefined,name: "focus",rawName: "v-focus",start: 73, value: ""}
]
複製代碼

存在el.directives屬性中,生成的渲染函數以下:app

with(this){return _c('div',{
        attrs:{"id":"app"}
    },[
        _c('input',{
            directives:
            [
                {
                    name:"model",
                    rawName:"v-model",
                    value:(keywords),
                    expression:"keywords"
                },
                {
                    name:"focus",
                    rawName:"v-focus"
                }
            ],
            staticClass:"form-control",
            attrs:{"type":"text"},
            domProps:{"value":(keywords)},
            on:{"input":function($event){
                if($event.target.composing)
                return;
                keywords=$event.target.value
            }
        }
    })
])}
複製代碼

input生成的Vnode以下:dom

{
    asyncFactory: undefined,
    asyncMeta: undefined,
    children: undefined,
    componentInstance: undefined,
    componentOptions: undefined,
    context: Vue {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue, …},
    data: {directives: Array(2), staticClass: "form-control", attrs: {type: "text"}, domProps: {value: undefined}, on: {input: f}},
    elm: undefined,
    fnContext: undefined,
    fnOptions: undefined,
    fnScopeId: undefined,
    isAsyncPlaceholder: false,
    isCloned: false,
    isComment: false,
    isOnce: false,
    isRootInsert: true,
    isStatic: false,
    key: undefined,
    ns: undefined,
    parent: undefined,
    raw: false,
    tag: "input",
    text: undefined,
    child: undefined
}
複製代碼

自定義指令在渲染階段

在渲染階段,patchVnode過程當中,會執行async

for (i = 0; i < cbs.update.length; ++i) { cbs.update[i](oldVnode, vnode); }
複製代碼

更新屬性的函數爲函數

[
    ƒ updateAttrs(oldVnode, vnode),
    ƒ updateClass(oldVnode, vnode),
    ƒ updateDOMListeners(oldVnode, vnode),
    ƒ updateDOMProps(oldVnode, vnode),
    ƒ updateStyle(oldVnode, vnode),
    ƒ update(oldVnode, vnode),
    ƒ updateDirectives(oldVnode, vnode)
]
複製代碼

其中指令相關的處理邏輯post

function updateDirectives (oldVnode, vnode) {
    if (oldVnode.data.directives || vnode.data.directives) {
      _update(oldVnode, vnode);
    }
}
複製代碼

_update函數爲:ui

function _update (oldVnode, vnode) {
    var isCreate = oldVnode === emptyNode;
    var isDestroy = vnode === emptyNode;
    var oldDirs = normalizeDirectives$1(oldVnode.data.directives, oldVnode.context);
    var newDirs = normalizeDirectives$1(vnode.data.directives, vnode.context);

    var dirsWithInsert = [];
    var dirsWithPostpatch = [];

    var key, oldDir, dir;
    for (key in newDirs) {
      oldDir = oldDirs[key];
      dir = newDirs[key];
      if (!oldDir) {
        // new directive, bind
        // 新的指令觸發bind方法
        callHook$1(dir, 'bind', vnode, oldVnode);
        if (dir.def && dir.def.inserted) {
          dirsWithInsert.push(dir);
        }
      } else {
        // existing directive, update
        // 指令存在,觸發update
        dir.oldValue = oldDir.value;
        dir.oldArg = oldDir.arg;
        callHook$1(dir, 'update', vnode, oldVnode);
        // 判斷是否設置componentUpdated方法,有則將其添加到列表中,確保組件的VNode和其子VNode所有更新後再調用指令的componentupdated方法
        if (dir.def && dir.def.componentUpdated) {
          dirsWithPostpatch.push(dir);
        }
      }
    }
    // 指令添加到dirsWithInsert,保證全部的bind執行完畢再執行insert函數
    if (dirsWithInsert.length) {
      var callInsert = function () {
        for (var i = 0; i < dirsWithInsert.length; i++) {
          callHook$1(dirsWithInsert[i], 'inserted', vnode, oldVnode);
        }
      };
      // 判斷虛擬節點是不是新建立節點
      if (isCreate) {
        // 能夠將一個鉤子函數與虛擬節點現有的鉤子函數合併在一塊兒,當虛擬節點觸發鉤子函數時,新增的鉤子也會觸發,這裏應該等到元素被插入到父節點後再執行指令的insert
        mergeVNodeHook(vnode, 'insert', callInsert);
      } else {
        // 不須要延遲到綁定元素插入到父節點後進行,直接執行callInsert
        callInsert();
      }
    }

    if (dirsWithPostpatch.length) {
        // 觸發update鉤子後,再觸發postpatch
      mergeVNodeHook(vnode, 'postpatch', function () {
        for (var i = 0; i < dirsWithPostpatch.length; i++) {
            // componentUpdated指令推遲到vnode和oldVnode更新後
          callHook$1(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode);
        }
      });
    }

    if (!isCreate) {
        // 新建立的虛擬節點不須要解綁,若是newDirs中不存在,則觸發unbind方法
      for (key in oldDirs) {
        if (!newDirs[key]) {
          // no longer present, unbind
          callHook$1(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy);
        }
      }
    }
}
複製代碼

這裏通過normalizeDirectives函數處理後變成:this

[
    {
        v-focus: {
            def: {bind: ƒ, inserted: ƒ, updated: ƒ},
            modifiers: {},
            name: "focus",
            oldArg: undefined,
            oldValue: undefined,
            rawName: "v-focus"
        }
    },
    v-model:{
        def: {inserted: ƒ, componentUpdated: ƒ},
        expression: "keywords",
        modifiers: {},
        name: "model",
        oldArg: undefined,
        oldValue: "wewe",
        rawName: "v-model",
        value: "wewew"
    }
]
複製代碼

callHook如何執行的鉤子函數以下:

function callHook$1 (dir, hook, vnode, oldVnode, isDestroy) {
    var 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"));
      }
    }
}
複製代碼

更新完directive後繼續進行diff,最後掛載到頁面。

相關文章
相關標籤/搜索