跳轉到內容

自定義指令

簡介

除了核心中包含的預設指令集(如 v-modelv-show),Vue 還允許您註冊自己的自定義指令。

我們在Vue中介紹了兩種程式碼複用形式:元件組合式。元件是主要構建塊,而組合式則專注於複用有狀態的邏輯。自定義指令另一方面,主要是為了複用在普通元素上涉及低階DOM訪問的邏輯。

自定義指令被定義為一個包含類似元件的生命週期鉤子的物件。這些鉤子接收指令繫結到的元素。以下是一個示例指令,當Vue將元素插入DOM時,它將聚焦輸入框:

vue
<script setup>
// enables v-highlight in templates
const vHighlight = {
  mounted: (el) => {
    el.classList.add('is-highlight')
  }
}
</script>

<template>
  <p v-highlight>This sentence is important!</p>
</template>
js
const highlight = {
  mounted: (el) => el.classList.add('is-highlight')
}

export default {
  directives: {
    // enables v-highlight in template
    highlight
  }
}
template
<p v-highlight>This sentence is important!</p>

這句話很重要!

在 `<script setup>` 中,任何以 v 字首開頭的 camelCase 變數都可以用作自定義指令。在上面的例子中,vHighlight 可以在模板中以 v-highlight 的形式使用。

如果不使用 `<script setup>`,則可以使用 directives 選項來註冊自定義指令。

js
export default {
  setup() {
    /*...*/
  },
  directives: {
    // enables v-highlight in template
    highlight: {
      /* ... */
    }
  }
}

與元件類似,自定義指令必須進行註冊,才能在模板中使用。在上面的例子中,我們透過 directives 選項進行本地註冊。

通常在應用級別全域性註冊自定義指令也很常見。

js
const app = createApp({})

// make v-highlight usable in all components
app.directive('highlight', {
  /* ... */
})

何時使用自定義指令

只有在需要透過直接 DOM 操作才能實現所需功能時,才應使用自定義指令。

此類的一個常見例子是 v-focus 自定義指令,可以將元素聚焦。

vue
<script setup>
// enables v-focus in templates
const vFocus = {
  mounted: (el) => el.focus()
}
</script>

<template>
  <input v-focus />
</template>
js
const focus = {
  mounted: (el) => el.focus()
}

export default {
  directives: {
    // enables v-focus in template
    focus
  }
}
template
<input v-focus />

此指令比 autofocus 屬性更有用,因為它不僅在工作頁面載入時有效,當元素被 Vue 動態插入時也有效!

當可能時,推薦使用帶有內建指令(如 v-bind)的宣告式模板,因為它們更高效且對伺服器端渲染友好。

指令鉤子

指令定義物件可以提供幾個鉤子函式(全部為可選)。

js
const myDirective = {
  // called before bound element's attributes
  // or event listeners are applied
  created(el, binding, vnode) {
    // see below for details on arguments
  },
  // called right before the element is inserted into the DOM.
  beforeMount(el, binding, vnode) {},
  // called when the bound element's parent component
  // and all its children are mounted.
  mounted(el, binding, vnode) {},
  // 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) {},
  // called when the parent component is unmounted
  unmounted(el, binding, vnode) {}
}

鉤子引數

指令鉤子會傳遞這些引數。

  • el:繫結到指令的元素。這可以用來直接操作 DOM。

  • binding:一個包含以下屬性的物件。

    • value:傳遞給指令的值。例如,在 v-my-directive="1 + 1" 中,值將是 2
    • oldValue:前一個值,僅在 beforeUpdateupdated 中可用。無論值是否更改,它都是可用的。
    • arg:傳遞給指令的引數(如果有)。例如,在 v-my-directive:foo 中,引數將是 "foo"
    • modifiers:包含修飾符的物件(如果有)。例如,在 v-my-directive.foo.bar 中,修飾符物件將是 { foo: true, bar: true }
    • instance:使用指令的元件例項。
    • dir:指令定義物件。
  • vnode:表示繫結元素的底層 VNode。

  • prevVnode:上一次渲染中表示繫結元素的 VNode。僅在 beforeUpdateupdated 鉤子中可用。

作為一個例子,考慮以下指令使用

template
<div v-example:foo.bar="baz">

binding 引數將是一個具有以下形狀的物件

js
{
  arg: 'foo',
  modifiers: { bar: true },
  value: /* value of `baz` */,
  oldValue: /* value of `baz` from previous update */
}

與內建指令類似,自定義指令的引數可以是動態的。例如

template
<div v-example:[arg]="value"></div>

這裡,指令引數將根據元件狀態中的 arg 屬性進行響應式更新。

注意

除了 el 之外,您應該將這些引數視為只讀的,並且永遠不要修改它們。如果您需要在鉤子之間共享資訊,建議透過元素的 dataset 來做。

函式簡寫

自定義指令通常對 mountedupdated 有相同的行為,不需要其他鉤子。在這種情況下,我們可以將指令定義為一個函式

template
<div v-color="color"></div>
js
app.directive('color', (el, binding) => {
  // this will be called for both `mounted` and `updated`
  el.style.color = binding.value
})

物件字面量

如果你的指令需要多個值,你也可以傳遞一個JavaScript物件字面量。記住,指令可以接受任何有效的JavaScript表示式。

template
<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!"
})

在元件中的用法

不推薦

不建議在元件上使用自定義指令。當元件有多個根節點時,可能會出現意外的行為。

在元件上使用時,自定義指令始終應用於元件的根節點,類似於 穿透屬性

template
<MyComponent v-demo="test" />
template
<!-- template of MyComponent -->

<div> <!-- v-demo directive will be applied here -->
  <span>My component content</span>
</div>

請注意,元件可能具有多個根節點。當應用於多根元件時,指令將被忽略並丟擲警告。與屬性不同,指令不能透過v-bind="$attrs"傳遞到不同的元素。

自定義指令已載入