###### tags: `Vue`
# 【Vue】自訂義指令 (Custom Directives)
Vue 提供了各式各樣的指令,像是 `v-model`、`v-show` 等等,同時 Vue 也有提供讓你自定義指令的方法。
指令是讓用來操作普通元素的底層 DOM 元素的程式邏輯變的可覆用。
自定義指令是一個包含生命週期(類似元件的生命週期)的物件,他會關連到指令想操作的 DOM 元素。
```html=
<script setup>
// enables v-focus in templates
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>
```
[example](https://vuejs.org/guide/reusability/custom-directives.html#introduction)
這裡我們自定義了一個指令叫做 v-focus,這個指令會在 DOM 元素載入後時自動對指定的元素進行 focus。這種指令會比 html 原生的 [`autofocus`](https://www.runoob.com/tags/att-input-autofocus.html) 屬性來的方便,因為即便是 Vue 動態載入的元素也能作用。
## 使用
任何以駝峰式命名的變數只要是 v 開頭的話,寫在 `<script setup>` 中都可以被當作是自定義指令。
假設不是在 SFC 中,使用 directives 也能註冊指令
```js=
export default {
setup() {
/*...*/
},
directives: {
// enables v-focus in template
focus: {
/* ... */
}
}
}
```
當然也能全域註冊
```js=
const app = createApp({})
// make v-focus usable in all components
app.directive('focus', {
/* ... */
})
```
:::info
關於自定義指令的使用時機: 如果你的程式有需要直接操作到 Dom 元素才推薦使用,其他情況比較推薦使用內建的 `v-bind` ,對於效能和 `server-rending` 比較友善。
:::
## 指令掛勾(Directive Hooks)
一個指令物件可以包含多個 hook 函式
```js=
const myDirective = {
// called before bound element's attributes
// or event listeners are applied
created(el, binding, vnode, prevVnode) {
// see below for details on arguments
},
// called right before the element is inserted into the DOM.
beforeMount(el, binding, vnode, prevVnode) {},
// called when the bound element's parent component
// and all its children are mounted.
mounted(el, binding, vnode, prevVnode) {},
// called before the parent component is updated
beforeUpdate(el, binding, vnode, prevVnode) {},
// called after the parent component and
// all of its children have updated
updated(el, binding, vnode, prevVnode) {},
// called before the parent component is unmounted
beforeUnmount(el, binding, vnode, prevVnode) {},
// called when the parent component is unmounted
unmounted(el, binding, vnode, prevVnode) {}
}
```
### Hook Arguments
* el: 指令會操作的 Dom 元素
* binding: 一個物件,包含以下屬性:
* value: 傳給指令的 value (ex: `v-my-directive="1 + 1"`, value: 2)
* oldValue: 只會出現在 `beforeUpdate` 和 `updated`,代表 update 之前的 value
* arg: 傳給指令的參數 (ex: `v-my-directive:foo`)
* modifiers: 包含 modifier 的物件 (ex: `v-my-directive.foo.bar`,modiflier: `{ foo: true, bar: true }`)
* instance: 指令操作的元件 instance
* dir: 指令物件
* vnode: 綁定普通元素的底層 [vnode](https://www.readfog.com/a/1644551841709658112)
* prevNode: 只能在 `beforeUpdate` 和 `updated` 中使用,代表前一次渲染綁定的 vnode。
ex:
```js=
<div v-example:foo.bar="baz">
```
binding 物件:
```js=
{
arg: 'foo',
modifiers: { bar: true },
value: /* value of `baz` */,
oldValue: /* value of `baz` from previous update */
}
```
與內建的指令相同,自定義指令的參數也能是動態的
```html=
<div v-example:[arg]="value"></div>
```
:::info
為了與 `el` 做出區別,這些 argument 都必須是唯讀,你也不該更改他們,如果你希望在 hook 之間分享資訊,建議改使用 html 原本就有的 dataset 屬性
:::
## 函式縮寫
自定義函式很長會在 `mounted` 和 `updated` 有相同行為,在其他 hook 沒有任何動作,所以 Vue 有提供一個縮寫方式:
```js=
app.directive('color', (el, binding) => {
// this will be called for both `mounted` and `updated`
el.style.color = binding.value
})
```
```html=
<div v-color="color"></div>
```
## [物件實字 (Object Literal)](https://ithelp.ithome.com.tw/articles/10208316)
如果你的指令需要傳入多個值,你可以傳入物件實字
>記住指令可以傳入任何的 javascript 表達式
```html=
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
```
```js=
app.directive('demo', (el, binding) => {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})
```
## 元件上的使用
在元件上使用時,也會像 Fallthrough Attritube 一樣,穿透到 root node。
```Vue=
<MyComponent v-demo="test" />
```
所以也和 Fallthrough Attritube 一樣,自定義指令會遇到多個 root node 的問題,如果遇到這種情況,Vue 會無視指令並留下警告,但不像 Fallthrough Attritube 一樣可以用 `v-bind=$attrs`來解決(其實也沒有解決方法),所以一般情況下不推薦直接在元件上使用自定義指令。
ex: https://blog.logrocket.com/deep-dive-custom-vue-directives/
https://codepen.io/_rahul/pen/mdBYREm?editors=1010