vue-learning:22 - js - directives

directives

在講解視圖層指令時,咱們講到ref特性,使用它咱們能夠獲取當前DOM元素對象,以便執行相關操做。html

<div id="app">
    <input ref="ipt" type="text" v-model="value" />
</div>
new Vue({
    el: "#app",
    data: {
        value: ''
    },
    methods: {
        handleEle() {
            let ele = this.$refs.ipt
            // do somthing
        }
    }
})

若是某個DOM操做在不一樣組件的元素或組件內多個元素都須要執行,像這樣在每一個組件裏都重複寫一遍處理邏輯代碼確定不是好辦法。此時咱們能夠自定義一個指令,在指令鉤子函數的回調裏複用邏輯代碼。vue

其時,自定義指令的思想仍是代碼複用的想法,同組件、混入、函數思想同樣。node

基本使用

根據指令使用範圍的不一樣,你能夠將指令定義在全局做用域、實例做用域或單個組件做用域內。express

// 在vue全局做用域
vue.directive('name', {
    bind: function (el, binding,vnode){/* do something */},
    inserted: function (el, binding,vnode){/* do something */},
    update: function (el, binding,vnode, oldVnode){/* do something */},
    componentUpdated: function (el, binding,vnode, oldVnode){/* do something */},
    unbind: function (el, binding,vnode){/* do something */},
})

// 在實例做用域中
var vm = new Vue({
    directives: {
        'name': {
            bind: function (el, binding,vnode){/* do something */},
            inserted: function (el, binding,vnode){/* do something */},
            update: function (el, binding,vnode, oldVnode){/* do something */},
            componentUpdated: function (el, binding,vnode, oldVnode){/* do something */},
            unbind: function (el, binding,vnode){/* do something */},
        }
    }
})

// 在組件做用域中
export default {
    directives: {
        'name': {
            bind: function (el, binding,vnode){/* do something */},
            inserted: function (el, binding,vnode){/* do something */},
            update: function (el, binding,vnode, oldVnode){/* do something */},
            componentUpdated: function (el, binding,vnode, oldVnode){/* do something */},
            unbind: function (el, binding,vnode){/* do something */},
        }
    }
}

指令鉤子函數

其中,指令有多個鉤子函數,正如vue實例和組件有生命週期鉤子函數同樣。它們有不一樣的命名,和不一樣觸發時機。api

  • bind: 鉤子函數會在指令綁定到元素時調用。只調用一次,指令第一次綁定到元素時調用。在這裏能夠進行一次性的初始化設置。
  • inserted:鉤子函數會在綁定的元素被添加到父節點時調用。可是和mounted同樣,此進還不能保證元素已經被添加到父節點上(僅保證父節點存在)。可使用this.$nextTick來保證這一點。
  • update:鉤子函數在綁定該指令的組件vnode節點被更新時調用,可是該組件的子組件的vnode可能此時還未更新。可是你能夠經過比較更新先後的值(vnode, oldVnode)來忽略沒必要要的模板更新 (詳細的鉤子函數參數見下)
  • componentUpdated:鉤子函數會在組件的 VNode 及其子組件的 VNode 所有更新後調用。
  • unbind:鉤子函數用於指令的拆除,只調用一次,當指令從元素上解綁時調用。

但不是每次自定義指令的時候須要調用全部鉤子,事實上,它們都是可選的,你能夠根據自定義指令的需求肯定指令回調函數執行的時機,來綁定特定的時機的鉤子上。基本最經常使用的就是bindupdate。針對最經常使用的有簡寫的方式,直接傳入一函數,但不建議,代碼中最好明確指令綁定的鉤子。數組

鉤子函數的參數

五個鉤子的回調函數都包含3個參數elbindingvnode。其中updatecomponentUpdate鉤子函數包含第4個參數oldVnodeapp

vue.directive('name', {
    bind: function (el, binding, vnode){/* do something */},
    inserted: function (el, binding, vnode){/* do something */},
    update: function (el, binding, vnode, oldVnode){/* do something */},
    componentUpdated: function (el, binding, vnode, oldVnode){/* do something */},
    unbind: function (el, binding, vnode){/* do something */},
})

咱們經過一個自定義指令例子來理解每一個參數具體的含義:
指令的使用都須要加上v,而且以-連字符鏈接。如v-namev-my-nameasync

<div v-my-directive:example.one.two = 'someExpression'>自定義指令示例<div>
// 全局註冊
vue.directive('my-directive', {
    bind: function (el, binding, vnode){/* do something */},
})
  • el:指令所綁定的DOM元素,能夠用來直接操做DOM對象。
  • vnode: Vue編譯生成的虛擬節點,具體API見下文。
    • 可使用vnode.context來獲取當前指令所處的上下文對象this,這個屬性頗有用
    • vnode.ele來獲取該節點元素,但必須在vnode.context.$nextTick(() => console.log(vnode.elm.parentNode))獲得保證。
  • oldVnode:上一個虛擬節點,僅在 updatecomponentUpdated 鉤子中可用。
  • binding:對象,包含如下該指令的相關屬性。具體以下:
    • name:指令的名稱,但不包含v-部分,在上例中值爲my-directive
    • arg:指令的參數,即example
    • modifiers: 對象,指令的修飾符對象。即{one:true, two:true}
    • expression: 指令表達式的字符串形式。即"someExpression"
    • value: 指令表達式計算的結果,即value = someExpression。若是data對象中。{someExpression: (3/4).toFixed(2),則value = o.75
    • oldVlue:指令上一次的value值。它只在updatecomponentUpdated 鉤子中可用。

文件的組織

通常咱們使用指令,也就是由於有頻繁操做,因此在項目中通常都是全局註冊。在項目src文件中定義一個directives文件夾,新建一個index.js函數

import Vue from 'vue'

var hasPerm = {
    bind(el, binding, vnode){
        let permissionList = vnode.context.$route.meta.permission

        if(!permissionList){
            console.error(`權限判斷不生效。因路由中不包含permission字段,請檢查路由表設置。當前路由地址:${vnode.context.$route.path}`)
            return
        }
        if(typeof (permissionList) != "object" || !permissionList.length){
            console.error(`權限判斷不生效。因路由中permission字段非數組類型或內容爲空,請檢查路由表設置。當前路由地址:${vnode.context.$route.path}`)
            return
        }
        if(!permissionList.includes(binding.value)){
            el.parentNode.removeChild(el)
        }
    }
}

var directives = {
    hasPerm
}

for (item in directives) {
    Vue.directive(item, directives[item])
}

export default Vue
// 在main.js文件導入
import './filters/install'

或者將自定義指令寫成插件形式,註冊到Vuethis

const hasPermission = {
    install: function (Vue){
        Vue.directive('hasPermission', {
            bind(el, binding, vnode){
                let permissionList = vnode.context.$route.meta.permission
                if(!permissionList){
                    console.error(`權限判斷不生效。因路由中不包含permission字段,請檢查路由表設置。當前路由地址:${vnode.context.$route.path}`)
                    return
                }
                if(typeof (permissionList) != "object" || !permissionList.length){
                    console.error(`權限判斷不生效。因路由中permission字段非數組類型或內容爲空,請檢查路由表設置。當前路由地址:${vnode.context.$route.path}`)
                    return
                }
                if(!permissionList.includes(binding.value)){
                    el.parentNode.removeChild(el)
                }
            }
        })
    }
}

export default hasPermission
// 全部自定義的插件都在plugins文件夾的install.js完成註冊
import Vue from 'vue'
import hasPermission from './hasPermission'

const Plugins = [
    hasPermission
]

// 全局註冊插件
Plugins.map((plugin) => {
    Vue.use(plugin)
})

export default Vue
// 在main.js文件導入
import './plugins/install'

擴展知識:vnode API

vnode參數

// 源碼路徑: vue/scr/vnode/vnode.js
export default class VNode {
    // outer
    tag: string | void;
    data: VNodeData | void;
    children: ?Array<VNode>;
    text: string | void;
    elm: Node | void;
    ns: string | void;
    context: Component | void; // rendered in this component's scope
    key: string | number | void;
    componentOptions: VNodeComponentOptions | void;
    componentInstance: Component | void; // component instance
    parent: VNode | void; // component placeholder node

    // strictly internal
    raw: boolean; // contains raw HTML? (server only)
    isStatic: boolean; // hoisted static node
    isRootInsert: boolean; // necessary for enter transition check
    isComment: boolean; // empty comment placeholder?
    isCloned: boolean; // is a cloned node?
    isOnce: boolean; // is a v-once node?
    asyncFactory: Function | void; // async component factory function
    asyncMeta: Object | void;
    isAsyncPlaceholder: boolean;
    ssrContext: Object | void;
    fnContext: Component | void; // real context vm for functional nodes
    fnOptions: ?ComponentOptions; // for SSR caching
    devtoolsMeta: ?Object; // used to store functional render context for devtools
    fnScopeId: ?string; // functional scope id support

    // ... 其它代碼
}
相關文章
相關標籤/搜索