1. 程式人生 > >Vue自定義指令directive,外掛的封裝以及混合mixins

Vue自定義指令directive,外掛的封裝以及混合mixins

一.自定義指令directive

除了核心功能預設內建的指令 (v-model 和 v-show),Vue 也允許註冊自定義指令。注意,在 Vue2.0 中,程式碼複用和抽象的主要形式是元件。然而,有的情況下,你仍然需要對普通 DOM 元素進行底層操作,這時候就會用到自定義指令。

來個例項,當頁面載入時,該input元素將獲得焦點:

// 註冊一個全域性自定義指令 `v-focus`
Vue.directive('focus', {
  // 當被繫結的元素插入到 DOM 中時……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})

如果想註冊區域性指令,元件中也接受一個 directives 的選項:

directives: {
  focus: {
    // 指令的定義
    inserted: function (el) {
      el.focus()
    }
  }
}

然後你可以在模板中任何元素上使用新的 v-focus 屬性,如下:

<input v-focus>

鉤子函式

一個指令定義物件可以提供如下幾個鉤子函式 (均為可選):

  • bind:只調用一次,指令第一次繫結到元素時呼叫。在這裡可以進行一次性的初始化設定。
  • inserted
    :被繫結元素插入父節點時呼叫 (僅保證父節點存在,但不一定已被插入文件中)。
  • update:所在元件的 VNode 更新時呼叫,但是可能發生在其子 VNode 更新之前。指令的值可能發生了改變,也可能沒有。但是你可以通過比較更新前後的值來忽略不必要的模板更新 (詳細的鉤子函式引數見下)。
  • componentUpdated:指令所在元件的 VNode 及其子 VNode 全部更新後呼叫。
  • unbind:只調用一次,指令與元素解綁時呼叫。

接下來我們來看一下鉤子函式的引數 (即 elbindingvnode 和 oldVnode)。

鉤子函式引數

指令鉤子函式會被傳入以下引數:

  • el:指令所繫結的元素,可以用來直接操作 DOM 。
  • binding:一個物件,包含以下屬性:

    • name:指令名,不包括 v- 字首。
    • value:指令的繫結值,例如:v-my-directive="1 + 1" 中,繫結值為 2
    • oldValue:指令繫結的前一個值,僅在 update 和 componentUpdated 鉤子中可用。無論值是否改變都可用。
    • expression:字串形式的指令表示式。例如 v-my-directive="1 + 1" 中,表示式為 "1 + 1"
    • arg:傳給指令的引數,可選。例如 v-my-directive:foo 中,引數為 "foo"
    • modifiers:一個包含修飾符的物件。例如:v-my-directive.foo.bar 中,修飾符物件為 { foo: true, bar: true }
  • vnodeVue 編譯生成的虛擬節點。移步 VNode API 來了解更多詳情。
  • oldVnode:上一個虛擬節點,僅在 update 和 componentUpdated 鉤子中可用。

除了 el 之外,其它引數都應該是隻讀的,切勿進行修改。如果需要在鉤子之間共享資料,建議通過元素的 dataset 來進行。

這是一個使用了這些屬性的自定義鉤子樣例:

<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
Vue.directive('demo', {
  bind: function (el, binding, vnode) {
    var s = JSON.stringify
    el.innerHTML =
      'name: '       + s(binding.name) + '<br>' +
      'value: '      + s(binding.value) + '<br>' +
      'expression: ' + s(binding.expression) + '<br>' +
      'argument: '   + s(binding.arg) + '<br>' +
      'modifiers: '  + s(binding.modifiers) + '<br>' +
      'vnode keys: ' + Object.keys(vnode).join(', ')
  }
})

new Vue({
  el: '#hook-arguments-example',
  data: {
    message: 'hello!'
  }
})

結果:

name: "demo"
value: "hello!"
expression: "message"
argument: "foo"
modifiers: {"a":true,"b":true}
vnode keys: tag, data, children, text, elm, ns, context, fnContext, fnOptions, fnScopeId, key, componentOptions, componentInstance, parent, raw, isStatic, isRootInsert, isComment, isCloned, isOnce, asyncFactory, asyncMeta, isAsyncPlaceholder

在很多時候,你可能想在 bind 和 update 時觸發相同行為,而不關心其它的鉤子。比如這樣寫:

Vue.directive('color-swatch', function (el, binding) {
  el.style.backgroundColor = binding.value
})

物件字面量

如果指令需要多個值,可以傳入一個 JavaScript 物件字面量。記住,指令函式能夠接受所有合法的 JavaScript 表示式。

<div v-demo="{ color: 'white', text: 'hello!' }"></div>
Vue.directive('demo', function (el, binding) {
  console.log(binding.value.color) // => "white"
  console.log(binding.value.text)  // => "hello!"
})

二.外掛

外掛通常會為 Vue 新增全域性功能。外掛的範圍沒有限制——一般有下面幾種:

  • 1.新增全域性方法或者屬性,如: vue-custom-element
  • 2.新增全域性資源:指令/過濾器/過渡等,如 vue-touch
  • 3.通過全域性 mixin 方法新增一些元件選項,如: vue-router
  • 4.新增 Vue 例項方法,通過把它們新增到 Vue.prototype 上實現。
  • 5.一個庫,提供自己的 API,同時提供上面提到的一個或多個功能,如 vue-router

Vue.js 的外掛應當有一個公開方法 install 。這個方法的第一個引數是 Vue 構造器,第二個引數是一個可選的選項物件:

MyPlugin.install = function (Vue, options) {
  // 1. 新增全域性方法或屬性
  Vue.myGlobalMethod = function () {
    // 邏輯...
  }

  // 2. 新增全域性資源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 邏輯...
    }
    ...
  })

  // 3. 注入元件
  Vue.mixin({
    created: function () {
      // 邏輯...
    }
    ...
  })

  // 4. 新增例項方法
  Vue.prototype.$myMethod = function (methodOptions) {
    // 邏輯...
  }
}

怎樣使用外掛

通過全域性方法 Vue.use() 使用外掛:

// 呼叫 `MyPlugin.install(Vue)`
Vue.use(MyPlugin)

也可以傳入一個選項物件:

Vue.use(MyPlugin, { someOption: true })

Vue.use 會自動阻止多次註冊相同外掛,屆時只會註冊一次該外掛。

Vue.js 官方提供的一些外掛 (例如 vue-router) 在檢測到 Vue 是可訪問的全域性變數時會自動呼叫 Vue.use()。然而在例如 CommonJS 的模組環境中,你應該始終顯式地呼叫 Vue.use()

// 用 Browserify 或 webpack 提供的 CommonJS 模組環境時
var Vue = require('vue')
var VueRouter = require('vue-router')

// 不要忘了呼叫此方法
Vue.use(VueRouter)

簡單例子

封裝一個全域性的外掛,如下:
在src下的components資料夾下新建一個countdown資料夾,新建一個countdown.vue檔案:

<template>
    <div>封裝一個最簡單的外掛</div>
</template>
<script>
export default{
    name:'count-down'
}
</script>

在放countdown.vue的同級目錄下新建一個index.js:

import countDown from './countdown';

countDown.install = function(Vue){
    Vue.component(countDown.name,countDown)
};

export default countDown;

在main.js中:

import countDown from './components/countdown/index.js' 
Vue.use(countDown)

在元件中就可以這樣使用了:

<count-down></count-down>

三.混合mixins

混合 (mixins) 是一種分發 Vue 元件中可複用功能的非常靈活的方式。混合物件可以包含任意元件選項。當元件使用混合物件時,所有混合物件的選項將被混入該元件本身的選項。

// 定義一個混合物件
var myMixin = {
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin!')
    }
  }
}

// 定義一個使用混合物件的元件
var Component = Vue.extend({
  mixins: [myMixin]
})

var component = new Component() // => "hello from mixin!"

選項合併

當元件和混合物件含有同名選項時,這些選項將以恰當的方式混合。比如,同名鉤子函式將混合為一個數組,因此都將被呼叫。另外,混合物件的 鉤子將在元件自身鉤子 之前 呼叫 :

var mixin = {
  created: function () {
    console.log('混合物件的鉤子被呼叫')
  }
}

new Vue({
  mixins: [mixin],
  created: function () {
    console.log('元件鉤子被呼叫')
  }
})

// => "混合物件的鉤子被呼叫"
// => "元件鉤子被呼叫"

值為物件的選項,例如 methodscomponents 和 directives,將被混合為同一個物件。兩個物件鍵名衝突時,取元件物件的鍵值對。

var mixin = {
  methods: {
    foo: function () {
      console.log('foo')
    },
    conflicting: function () {
      console.log('from mixin')
    }
  }
}

var vm = new Vue({
  mixins: [mixin],
  methods: {
    bar: function () {
      console.log('bar')
    },
    conflicting: function () {
      console.log('from self')
    }
  }
})

vm.foo() // => "foo"
vm.bar() // => "bar"
vm.conflicting() // => "from self"

注意:Vue.extend() 也使用同樣的策略進行合併。

全域性混合

也可以全域性註冊混合物件。注意使用! 一旦使用全域性混合物件,將會影響到 所有 之後建立的 Vue 例項。使用恰當時,可以為自定義物件注入處理邏輯。

// 為自定義的選項 'myOption' 注入一個處理器。
Vue.mixin({
  created: function () {
    var myOption = this.$options.myOption
    if (myOption) {
      console.log(myOption)
    }
  }
})

new Vue({
  myOption: 'hello!'
})
// => "hello!"

謹慎使用全域性混合物件,因為會影響到每個單獨建立的 Vue 例項 (包括第三方模板)。大多數情況下,只應當應用於自定義選項,就像上面示例一樣。也可以將其用作 Plugins 以避免產生重複應用

自定義選項合併策略

自定義選項將使用預設策略,即簡單地覆蓋已有值。如果想讓自定義選項以自定義邏輯合併,可以向 Vue.config.optionMergeStrategies 新增一個函式:

Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) {
  // return mergedVal
}

對於大多數物件選項,可以使用 methods 的合併策略:

var strategies = Vue.config.optionMergeStrategies
strategies.myOption = strategies.met