###### 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