1. 前言
本小節我們介紹 Vue 中的自定義指令。包括全局指令的注冊、局部指令的注冊、指令鉤子函數的使用以及動態指令傳參。其中,指令鉤子函數和動態指令參數是本節的難點。
同學們需要充分理解每個指令鉤子函數執行的時機、對動態指令參數多加練習才能對指令的使用得心應手。
2. 慕課解釋
Vue 除了提供了默認內置的指令外,還允許開發人員根據實際情況自定義指令,它的作用價值在于當開發人員在某些場景下需要對普通 DOM 元素進行底層操作的時候。 – 官方定義
在之前的章節中我們學習了指令 v-show,他的實現原理就是操作 DOM 元素的樣式 display,使之實現隱藏、顯示的效果。在日常開發中,我們經常把一些對 DOM 大量相同的操作封裝成指令。學好指令可以給我們的開發帶來便利、提高效率。同學們需要總結業務中的各種場景,多加練習。
3. 注冊自定義指令
Vue 自定義指令和組件一樣存在著全局注冊和局部注冊兩種方式。全局注冊的自定義指令可以在項目中的所有組件中使用,局部注冊的指令只能在當前組件內部使用。接下來我們分步介紹全局指令和局部指令的注冊方式。
3.1 全局注冊
我們可以通過調用 Vue.directive
的方式來定義全局指令, 它接收兩個參數:1. 指令名,2. 指令的鉤子函數對象。
命名:
- 短橫線:
<my-directive>
- 駝峰式:
<MyDirective>
使用駝峰命名指令時,首字母最好以大寫字母開頭。
鉤子函數對象:指令的鉤子函數對象我們將在下面段落 4 中詳細介紹。
注意:注冊指令時,指令名稱不需要加 v- 前綴,默認是自動加上前綴的,使用指令的時候一定要加上 v- 前綴。
// 注冊
// 駝峰命名
Vue.directive('MyDirective', {/* */})
// 短橫線命名
Vue.directive('my-directive', {/* */})
// 使用
<div v-my-directive></div>
下面我們注冊一個全局指令 v-focus,該指令的功能是在頁面加載時,使得元素獲得焦點。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<div>
<label>姓名:</label>
<input id="name" v-focus type="text"/>
</div>
<div>
<label>年齡:</label>
<input id="age" type="text"/>
</div>
</div>
</body>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script type="text/javascript">
Vue.directive('focus', {
inserted(el) {
el.focus()
}
})
var vm = new Vue({
el: '#app',
data() {
return {}
}
})
</script>
</html>
代碼解釋:
JS 代碼第 3-7 行,我們定義了指令 v-focus,定義 inserted 鉤子函數,在節點被插入時獲得焦點。
HTML 代碼第 4 行,我們在 input
元素上使用指令,當頁面打開時 id 為 name 的輸入框會自動獲取焦點。
3.2 局部注冊
指令的局部注冊和組件的局部注冊類似,在實例的參數 options
中使用 directives
選項來注冊局部指令,局部指令只能在當前這個實例中使用:
// 注冊
// 短橫線命名
{
directives: {
'my-directive': {
inserted: function (el) {
el.focus()
}
}
}
}
// 駝峰命名
{
directives: {
'MyDirective': {
inserted: function (el) {
el.focus()
}
}
}
}
// 使用
<div v-my-directive></div>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<div>
<label>姓名:</label>
<input v-focus type="text"/>
</div>
<div>
<label>年齡:</label>
<input type="text"/>
</div>
</div>
</body>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data() {
return {}
},
directives: {
focus: {
inserted: function (el) {
el.focus()
}
}
}
})
</script>
</html>
代碼解釋:
JS 代碼第 8-14 行,我們定義了局部指令 v-focus,定義 inserted 鉤子函數,在節點被插入時獲得焦點。
HTML 代碼第 4 行,我們在 input
元素上使用指令,當頁面打開時 id 為 name 的輸入框會自動獲取焦點。
4. 鉤子函數
上面我們介紹了 Vue.directive 第二個參數接收的是鉤子函數對象,這些鉤子函數都是可選的。接下來我們詳細介紹這幾個鉤子函數的作用:
bind
:只調用一次,指令第一次綁定到元素時調用,在這里可以進行一次性的初始化設置;inserted
:被綁定元素插入父節點時調用 (僅保證父節點存在,但不一定已被插入文檔中);update
:所在組件的 VNode 更新時調用,但是可能發生在其子 VNode 更新之前。指令的值可能發生了改變,也可能沒有。但是你可以通過比較更新前后的值來忽略不必要的模板更新 (詳細的鉤子函數參數見下);componentUpdated
:指令所在組件的 VNode 及其子 VNode 全部更新后調用;unbind
:只調用一次,指令與元素解綁時調用。
4.1 鉤子函數參數
指令鉤子函數會被傳入以下參數:
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 }
。
vnode
:Vue 編譯生成的虛擬節點。移步 VNode API 來了解更多詳情;oldVnode
:上一個虛擬節點,僅在update
和componentUpdated
鉤子中可用。
4.2 動態指令參數
指令的參數可以是動態的。例如,在 v-mydirective:[argument]=“value” 中,argument 參數可以根據組件實例數據進行更新!這使得自定義指令可以在應用中被靈活使用。
例如你想要創建一個自定義指令,用來改變頁面元素的字體顏色。我們可以像這樣創建一個通過指令值來更新字體顏色的自定義指令:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<div v-color="color">Hello !</div>
<button @click="changeColor">切換顏色</button>
</div>
</body>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script type="text/javascript">
Vue.directive('color', {
bind: function (el, binding, vnode) {
el.style.color = binding.value
},
update(el, binding) {
el.style.color = binding.value
}
})
var vm = new Vue({
el: '#app',
data() {
return {
color: 'red'
}
},
methods: {
changeColor() {
this.color = '#' + Math.floor( Math.random() * 0xffffff ).toString(16);
}
}
})
</script>
</html>
代碼解釋:
JS 代碼第 3-10 行,我們定義了全局指令 v-color,定義 bind 鉤子函數設置元素的字體顏色,定義 update 鉤子函數,在節點更新時修改元素的字體顏色。
HTML 代碼第 2 行,我們使用 v-color 指令,并動態傳入值 color。
HTML 代碼第 3 行,點擊按鈕切換 color 的值。
最終,當我們點擊按鈕時,“Hello !” 的字體顏色會隨機變化。
上面的例子中我們通過指令動態設置了元素的字體顏色。但如果場景是我們需要修改元素的邊框顏色又該怎么辦呢?有些同學可能說我們可以再寫一個 v-border-color 不就行了。那如果又有修改背景色的需求呢?這時使用動態參數就可以非常方便地根據每個組件實例來進行更新:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<div v-color:[colorstyle]="color" style="border: 1px solid #ccc;">Hello !</div>
<div v-color:[borderstyle]="color" style="border: 1px solid #ccc;">Hello !</div>
<div v-color:[backgroundstyle]="color" style="border: 1px solid #ccc;">Hello !</div>
<button @click="changeColor">切換顏色</button>
</div>
</body>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script type="text/javascript">
Vue.directive('color', {
bind: function (el, binding, vnode) {
var s = binding.arg
el.style[s] = binding.value
},
update(el, binding) {
var s = binding.arg
el.style[s] = binding.value
}
})
var vm = new Vue({
el: '#app',
data() {
return {
color: 'red',
colorstyle: 'color',
borderstyle: 'border-color',
backgroundstyle: 'background-color',
}
},
methods: {
changeColor() {
this.color = '#' + Math.floor( Math.random() * 0xffffff ).toString(16);
}
}
})
</script>
</html>
代碼解釋:
JS 代碼第 3-12 行,我們定義了全局指令 v-color,定義 bind 鉤子函數和 update 鉤子函數。
HTML 代碼第 2-4 行,我們使用 v-color 指令,并動態傳入值 color。
HTML 代碼第 5 行,點擊按鈕切換 color 的值。
最終,當我們點擊 "切換顏色" 按鈕時,分別會修改元素的 color、border-color、background-color 樣式屬性。
4.3 對象字面量
如果指令需要多個值,可以傳入一個 JavaScript 對象字面量。記住,指令函數能夠接受所有合法的 JavaScript 表達式:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<div v-font="font">Hello !</div>
</div>
</body>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script type="text/javascript">
Vue.directive('font', {
bind: function (el, binding, vnode) {
el.style.color = binding.value.color
el.style['font-size'] = binding.value.size
}
})
var vm = new Vue({
el: '#app',
data() {
return {
font: {
size: '26px',
color: 'red'
}
}
}
})
</script>
</html>
代碼解釋:
JS 代碼第 3-8 行,我們定義了全局指令 v-font。
JS 代碼第 13-16 行,我們定義了對象類型的值 font。
HTML 代碼第 2 行,我們使用 v-font 指令,并動態傳入對象類型的值 font。
5. 小結
本節,我們帶大家學習了自定義指令在項目中的運用。主要知識點有以下幾點:
- 使用 Vue.directive 注冊全局組件。
- 使用 Vue 實例上 directives 屬性注冊局部組件。
- 使用指令中鉤子函數操作 DOM 元素。
- 使用動態指令參數對 DOM 元素做不同的操作。