跳转到内容

自定义指令

简介

除了核心中包含的默认指令集(如 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"传递到不同的元素。

自定义指令已加载