Перейти к содержанию

Пользовательские директивы

Введение

Помимо стандартного набора директив, поставляемых в ядре (например, v-model или v-show), Vue также позволяет регистрировать собственные директивы.

Мы представили две формы повторного использования кода во Vue: компоненты и композаблы. Компоненты являются основными строительными блоками, в то время как композаблы сосредоточены на повторном использовании логики, основанной на состоянии. Пользовательские директивы, с другой стороны, в основном предназначены для повторного использования логики, которая включает низкоуровневый доступ к DOM для простых элементов.

Пользовательская директива определяется как объект, содержащий хуки жизненного цикла, аналогичные тем, что используются в компоненте. Эти хуки получают элемент, к которому привязана директива. Вот пример директивы, которая добавляет класс к элементу, когда он вставляется в DOM с помощью Vue:

vue
<script setup>
// включает v-highlight в шаблонах
const vHighlight = {
  mounted: (el) => {
    el.classList.add('is-highlight')
  }
}
</script>

<template>
  <p v-highlight>Это предложение очень важно!</p>
</template>
js
const highlight = {
  mounted: (el) => el.classList.add('is-highlight')
}

export default {
  directives: {
    // включает v-highlight в шаблонах
    highlight
  }
}
template
<p v-highlight>Это предложение очень важно!</p>

Это предложение очень важно!

В <script setup> любая переменная в «верблюжьем» регистре, начинающаяся с префикса v, может быть использована в качестве пользовательской директивы. В приведённом выше примере vHighlight можно использовать в шаблоне как v-highlight.

Если не используется <script setup>, пользовательские директивы могут быть зарегистрированы с помощью опции directives:

js
export default {
  setup() {
    /*...*/
  },
  directives: {
    // включает v-highlight в шаблонах
    highlight: {
      /* ... */
    }
  }
}

Как и компоненты, пользовательские директивы должны быть зарегистрированы, чтобы их можно было использовать в шаблонах. В приведённом выше примере мы используем локальную регистрацию с помощью опции directives.

Также часто встречается глобальная регистрация пользовательских директив на уровне приложения:

js
const app = createApp({})

// делаем v-highlight пригодным для использования во всех компонентах
app.directive('highlight', {
  /* ... */
})

Когда использовать пользовательские директивы

Пользовательские директивы следует использовать только в тех случаях, когда требуемая функциональность может быть достигнута только путём прямого манипулирования DOM.

Частым примером этого является пользовательская директива v-focus, которая переводит элемент в фокус.

vue
<script setup>
// включает v-focus в шаблонах
const vFocus = {
  mounted: (el) => el.focus()
}
</script>
<template>
  <input v-focus />
</template>
js
const focus = {
  mounted: (el) => el.focus()
}
export default {
  directives: {
    // включает v-focus в шаблонах
    focus
  }
}
template
<input v-focus />

Эта директива более полезна, чем атрибут autofocus, потому что она работает не только при загрузке страницы, но и когда элемент динамически вставляется Vue!

Декларативный шаблонизатор со встроенными директивами, такими как v-bind, рекомендуется использовать, когда это возможно, поскольку они более эффективны и удобны для серверного рендеринга.

Хуки директив

Объект определения директивы может предоставлять несколько функций хука (все они необязательны):

js
const myDirective = {
  // вызывается до применения атрибутов связанного
  // элемента или слушателей событий
  created(el, binding, vnode) {
    // подробнее об аргументах см. ниже
  },
  // вызывается непосредственно перед вставкой элемента в DOM.
  beforeMount(el, binding, vnode) {},
  // вызывается, когда родительский компонент связанного элемента
  // и все его дочерние элементы смонтированы.
  mounted(el, binding, vnode) {},
  // вызывается перед обновлением родительского компонента
  beforeUpdate(el, binding, vnode, prevVnode) {},
  // вызывается после того, как родительский компонент
  // и все его дочерние компоненты обновятся
  updated(el, binding, vnode, prevVnode) {},
  // вызывается перед размонтированием родительского компонента
  beforeUnmount(el, binding, vnode) {},
  // вызывается, когда родительский компонент размонтирован
  unmounted(el, binding, vnode) {}
}

Аргументы хуков

Эти аргументы передаются хукам директив:

  • el: элемент, к которому привязана директива. Это можно использовать для прямого манипулирования DOM.

  • binding: объект, содержащий следующие свойства.

    • value: Значение, передаваемое директиве. Например, в v-my-directive="1 + 1" значение будет 2.
    • oldValue: Предыдущее значение, доступное только в beforeUpdate и updated. Оно доступно независимо от того, изменилось значение или нет.
    • arg: Аргумент, переданный директиве, если таковой имеется. Например, в v-my-directive:foo аргументом будет "foo".
    • modifiers: Объект, содержащий модификаторы, если таковые имеются. Например, в v-my-directive.foo.bar объект модификаторов будет иметь вид { foo: true, bar: true }.
    • instance: Экземпляр компонента, в котором используется директива.
    • dir: объект определения директивы.
  • vnode: базовый узел VNode, представляющий связанный элемент.

  • prevVnode: VNode, представляющий связанный элемент из предыдущего рендера. Доступно только в хуках beforeUpdate и updated.

В качестве примера рассмотрим использование следующей директивы:

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

Аргумент binding будет представлять собой объект в форме:

js
{
  arg: 'foo',
  modifiers: { bar: true },
  value: /* значение `baz` */,
  oldValue: /* значение `baz` из предыдущего обновления */
}

Подобно встроенным директивам, аргументы пользовательских директив могут быть динамическими. Например:

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

Здесь аргумент директивы будет реактивно обновляться на основе свойства arg в состоянии нашего компонента.

Примечание

Кроме el, вы должны рассматривать эти аргументы как предназначенные только для чтения и никогда не изменять их. Если вам нужно обмениваться информацией между хуками, рекомендуется делать это через dataset элемента.

Директива в виде функции

Обычно пользовательская директива имеет одинаковое поведение для mounted и updated и не нуждается в других хуках. В таких случаях мы можем определить директиву как функцию:

template
<div v-color="color"></div>
js
app.directive('color', (el, binding) => {
  // это будет вызвано для `mounted` и `updated`.
  el.style.color = binding.value
})

Литералы объектов

Если вашей директиве требуется несколько значений, вы также можете передать объектный литерал JavaScript. Помните, что директивы могут принимать любое правильное выражение JavaScript.

template
<div v-demo="{ color: 'white', text: 'привет!' }"></div>
js
app.directive('demo', (el, binding) => {
  console.log(binding.value.color) // => "white"
  console.log(binding.value.text) // => "привет!"
})

Использование в компонентах

Не рекомендуется

Использование пользовательских директив для компонентов не рекомендуется. При наличии у компонента нескольких корневых узлов может возникнуть непредвиденное поведение.

При использовании в компонентах пользовательские директивы всегда будут применяться к корневому узлу компонента, подобно обычным атрибутам.

template
<MyComponent v-demo="test" />
template
<!-- шаблон MyComponent -->

<div> <!-- Здесь будет применена директива v-demo -->
  <span>Содержание компонента</span>
</div>

Обратите внимание, что компоненты потенциально могут иметь более одного корневого узла. При применении к мультикорневому компоненту директива будет проигнорирована, и будет выдано предупреждение. В отличие от атрибутов, директивы не могут быть переданы другому элементу с помощью v-bind="$attrs".

Пользовательские директивы