directive(指令)是我們在 Vue 自定義的指令。當我們要重複處理某些工作,例如轉換時間呈現的格式的工作,可以使用 directive 來處理。我們可以在全局或局部註冊 directive,之後套在 DOM 元素或元件上使用。另外,註冊 directive 時,可以設定不同生命週期函式來執行程式碼。
以下會再作詳細解說。主要會以 Vue 3 作為主要示範例子,但也會補充一些 Vue 2 的做法。
全域:
main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.directive('focus', {
mounted(el) {
el.focus()
}
})
app.mount('#app')
局部(在元件裏註冊):
在元件中加入 directives
屬性:
directives: {
focus: {
mounted(el) {
el.focus()
}
}
}
註冊 directive 時,參數可以是物件或函式。如果你:
先說傳入物件的語法。
物件裏可以使用的不同生命週期函式。以下只列出兩個常用的,其餘的可參考這部分的官方文件。
app.directive('指令名稱', {
mounted(el, binding) {
// directive 所綁定的父元件已被掛載
},
updated(el, binding) {
// 元件的 VNode 和它子元件 VNode 更新後
}
})
這些生命週期函式會接受 4 個參數:
app.directive('focus', {
mounted(el, binding, vnode, prevNode) {
...
},
})
假設現在建立一個 v-focus
指令,並在指令裏傳入數值:
<input v-focus:foo="111" type="text">
Vue 裏有以下資料:
data() {
return {
name: 'Alysa',
job: 'developer'
}
}
然後用 console.log
把這個生命週期函式裏的所有參數顯示出來:
app.directive('focus', {
mounted(el, binding, vnode, prevNode) {
console.log(el);
console.log(binding);
console.log(vnode);
console.log(prevNode);
},
})
結果:
仔細看 binding 物件:
所以,參數的意思是:
el
: directive 所綁定的 DOM 元素。可以用它來操作 DOM。binding
:一個物件,裏面會有多個屬性。value
是在 directive 傳入的值,即是 v-focus="111"
的 111。arg
傳遞給 directive 的參數,即是 v-focus:foo
的 foo。instance
可以取得這個 directive 所在的元件的資料。注意,Vue 2 的生命週期函式的名稱與 Vue 3 明顯不同。
在 Vue 3 的 mounted
,在 Vue 2 會變為 inserted
。詳情參考 Vue 2 的 directive 文件。
使用函式的話,就簡單多了。如果你不需要設定其他生命週期函式,就建議使用函式作參數。這預設所有程式都在 mounted
裏執行。
app.directive('focus', (el) => {
// 在 mounted 時執行
el.focus()
})
參考 Mike 老師的文章,想起自己曾經從後端取來的時間格式,並不是畫面想呈現的格式。因此前端需要再把時間轉換為畫面所需的格式。但不可能每次都重複寫轉換時間的程式碼。因此這情況就很適合使用 directive。
假設前端的資料是 ISO 8601 格式的日期。
data() {
return {
currentDateTime: new Date().toISOString()
}
}
但網頁想呈現 DD-MM-YYYY
這格式,於是使用 directive 來轉換。
先註冊一個轉換時間的 directive,以下使用 moment
插件來轉換格式 :
app.directive('timeFormat', (el, binding) => {
el.textContent = moment(binding.value).format('DD-MM-YYYY')
})
最後在模版中套上 timeFormat directive 就大功告成:
<p v-timeFormat="currentDateTime"> {{ currentDateTime }} </p>
另外,Mike 老師在另一篇文章有解釋如何結合使用 directive 和 Skeleton loading 的製作,也推薦大家看看,非常實用。他把 directive 應用在 img 上,在 directive 裏透過使用 new Image().onload
的方式來判斷圖片是否已下載完成,如果是,就把圖片的 src 寫回 img 的 src 裏。否則就繼續套用 skeleton 樣式。
有個官方例子很有趣,就是動態修改元素的 position。
先看結果:
做法就是利用 directive 動態綁定此文字的 position 值以及方向。
套用 directive:
<p v-pin:[direction]="pinPadding">
Direction: {{ direction }} , pinPadding: {{ pinPadding }}
</p>
建立資料:
data() {
return {
direction: "top",
pinPadding: 100,
};
}
註冊 directive:
main.js
app.directive("pin", {
mounted(el, binding) {
el.style.position = "fixed";
el.style[binding.arg] = binding.value + "px";
},
updated(el, binding) {
// 避免 top 與 bottom 或 left 與 right 同時出現
switch (binding.arg) {
case "bottom":
el.style.top = "";
break;
case "top":
el.style.bottom = "";
break;
case "right":
el.style.left = "";
break;
case "left":
el.style.right = "";
break;
// no default
}
el.style[binding.arg] = binding.value + "px";
}
});
以上利用 JavaScript 的 element.style.方向 = ...px
來改變元素的位置。另外我跟官方例子不同的是,這例子有另外加上 radio 來選取方向。但考慮到如果 top 和 bottom,或者 right 和 left 值同時出現的話,就會出現數值衝突,以致文字沒有動的情況。因此我另外在 updated
函式裏加上 switch 來判斷。
https://codesandbox.io/s/directive-dong-tai-directive-yv1en?file=/src/App.vue
mounted
裏執行程式碼。[重構倒數第13天] - Vue3定義自己的模板語法
[重構倒數第12天] - Vue3 directive 與 Skeleton 實戰組合應用
Dynamic Directive Arguments