iT邦幫忙

2021 iThome 鐵人賽

DAY 17
0
Modern Web

不只懂 Vue 語法:Vue.js 觀念篇系列 第 17

不只懂 Vue 語法:什麼是 directive?請示範如何使用 directive?

  • 分享至 

  • xImage
  •  

問題回答

directive(指令)是我們在 Vue 自定義的指令。當我們要重複處理某些工作,例如轉換時間呈現的格式的工作,可以使用 directive 來處理。我們可以在全局或局部註冊 directive,之後套在 DOM 元素或元件上使用。另外,註冊 directive 時,可以設定不同生命週期函式來執行程式碼。

以下會再作詳細解說。主要會以 Vue 3 作為主要示範例子,但也會補充一些 Vue 2 的做法。

基本用法

在全域或局部註冊 directive

全域:

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 時,參數可以是物件或函式。如果你:

  1. 傳入物件:可以設定不同生命週期函式。
  2. 傳入函式:只在 mounted 執行。

1. 物件作為參數:可設定不同生命週期

先說傳入物件的語法。

物件裏可以使用的不同生命週期函式。以下只列出兩個常用的,其餘的可參考這部分的官方文件

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 文件

2. 函式作為參數:預設使用 mounted 生命週期

使用函式的話,就簡單多了。如果你不需要設定其他生命週期函式,就建議使用函式作參數。這預設所有程式都在 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>

其他有關 Skeleton 與 directive 的做法

另外,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

總結

  • directive 是自定義指令,可以全局或局部註冊。
  • 註冊 directive 時,可傳入物件或函式作參數。前者容許我們設定在不同生命週期裏執行程式碼。後者則只會在 mounted 裏執行程式碼。

參考資料

[重構倒數第13天] - Vue3定義自己的模板語法
[重構倒數第12天] - Vue3 directive 與 Skeleton 實戰組合應用
Dynamic Directive Arguments


上一篇
不只懂 Vue 語法:為什麼需要使用 $nextTick ?
下一篇
不只懂 Vue 語法:什麼是 slot?請示範 slot 的用法?
系列文
不只懂 Vue 語法:Vue.js 觀念篇31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言