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

# Введение

Кроме встроенных директив (таких как v-model и v-show), Vue позволяет добавлять и пользовательские. При этом важно понимать, что во Vue всё-таки компоненты являются основным механизмом для создания повторно используемого кода. Тем не менее, для выполнения низкоуровневых операций с DOM директивы могут быть очень полезны. Например для реализации фокуса на элементе input:

See the Pen Custom directives: basic example by Vue (@Vue) on CodePen.

После загрузки страницы этот элемент получает фокус ввода (примечание: autofocus не работает на мобильном Safari). Если с момента открытия этой страницы руководства ещё никуда не кликнули, то фокус ввода и сейчас должен быть на этом элементе. Также можно кликнуть по кнопке Rerun и фокус будет в поле ввода.

Давайте реализуем директиву, которая это сделает:

const app = Vue.createApp({})

// Регистрируем глобальную пользовательскую директиву `v-focus`
app.directive('focus', {
  // Когда привязанный элемент примонтирован в DOM...
  mounted(el) {
    // Переключаем фокус на элемент
    el.focus()
  }
})
1
2
3
4
5
6
7
8
9
10

Чтобы зарегистрировать директиву локально, можно передать опцию directives при определении компонента:

directives: {
  focus: {
    // определение директивы
    mounted(el) {
      el.focus()
    }
  }
}
1
2
3
4
5
6
7
8

После регистрации в шаблоне можно использовать новый атрибут v-focus:

<input v-focus />
1

# Хуки

Для жизненного цикла директивы можно указать следующие хуки (все они опциональны):

  • created: вызывается до привязки атрибутов или слушателей событий к элементу. Это полезно в тех случаях, когда директиве необходимо привязать слушатели событий, которые должны вызываться перед обычными слушателями событий v-on.

  • beforeMount: вызывается при первой привязке директивы к элементу и перед монтированием родительского компонента. Здесь можно выполнять какую-то единоразовую инициализацию.

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

  • beforeUpdate: вызывается перед обновлением VNode содержащего компонента.

Примечание

Подробнее VNode рассмотрим позднее, когда будем обсуждать render-функции.

  • updated: вызывается после того как VNode содержащего компонента и все VNode его дочерних элементов были обновлены.

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

  • unmounted: вызывается только один раз, когда директива отвязывается от элемента и родительский компонент размонтирован.

Подробнее об аргументах, которые передаются в эти хуки (например, el, binding, vnode и prevVnode) можно узнать в API приложения.

# Динамические аргументы директивы

Аргументы директивы могут быть динамическими. Например, для v-mydirective:[argument]="value", argument может обновляться в зависимости от свойства данных экземпляра компонента! Это позволит сделать пользовательские директивы более гибкими при использовании в приложении.

Допустим, необходимо создать собственную директиву, которая позволит установить элемент на странице с помощью фиксированного позиционирования. Можно создать пользовательскую директиву, где значение определяет вертикальный отступ в пикселях:

<div id="dynamic-arguments-example" class="demo">
  <p>Прокрутите страницу вниз</p>
  <p v-pin="200">Элемент зафиксирован в 200px от начала страницы</p>
</div>
1
2
3
4
const app = Vue.createApp({})

app.directive('pin', {
  mounted(el, binding) {
    el.style.position = 'fixed'
    // binding.value — значение передаваемое в директиву, в этом случае 200
    el.style.top = binding.value + 'px'
  }
})

app.mount('#dynamic-arguments-example')
1
2
3
4
5
6
7
8
9
10
11

Это закрепит элемент в 200px от начала страницы. Но что если возникнет случай, когда необходимо закрепить элемент слева, а не сверху? Для этого пригодится динамический аргумент директивы, который можно определить для каждого экземпляра компонента:

<div id="dynamicexample">
  <h3>Прокрутите страницу вниз</h3>
  <p v-pin:[direction]="200">Элемент зафиксирован в 200px слева от страницы.</p>
</div>
1
2
3
4
const app = Vue.createApp({
  data() {
    return {
      direction: 'right'
    }
  }
})

app.directive('pin', {
  mounted(el, binding) {
    el.style.position = 'fixed'
    // binding.arg — аргумент, передаваемый в директиву
    const s = binding.arg || 'top'
    el.style[s] = binding.value + 'px'
  }
})

app.mount('#dynamic-arguments-example')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Результат:

See the Pen Custom directives: dynamic arguments by Vue (@Vue) on CodePen.

Теперь пользовательская директива достаточно гибкая для использования в различных сценариях. Чтобы сделать её ещё более динамичной, позволим изменять значение отступа. Создадим дополнительное свойство pinPadding и привяжем его к <input type="range">.




 


<div id="dynamicexample">
  <h2>Scroll down the page</h2>
  <input type="range" min="0" max="500" v-model="pinPadding">
  <p v-pin:[direction]="pinPadding">Зафиксировать в {{ pinPadding + 'px' }} от {{ direction }} страницы</p>
</div>
1
2
3
4
5




 




const app = Vue.createApp({
  data() {
    return {
      direction: 'right',
      pinPadding: 200
    }
  }
})
1
2
3
4
5
6
7
8

Доработаем логику директивы для пересчёта расстояния при обновлении компонента:







 
 
 
 


app.directive('pin', {
  mounted(el, binding) {
    el.style.position = 'fixed'
    const s = binding.arg || 'top'
    el.style[s] = binding.value + 'px'
  },
  updated(el, binding) {
    const s = binding.arg || 'top'
    el.style[s] = binding.value + 'px'
  }
})
1
2
3
4
5
6
7
8
9
10
11

Результат:

See the Pen Custom directives: dynamic arguments + dynamic binding by Vue (@Vue) on CodePen.

# Сокращённая запись

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

app.directive('pin', (el, binding) => {
  el.style.position = 'fixed'
  const s = binding.arg || 'top'
  el.style[s] = binding.value + 'px'
})
1
2
3
4
5

# Передача объекта данных в директиву

В случае, если директива должна принимать несколько параметров, можно указать объект JavaScript — годится любое валидное выражение, помните?

<div v-demo="{ color: 'белый', text: 'привет!' }"></div>
1
app.directive('demo', (el, binding) => {
  console.log(binding.value.color) // => "белый"
  console.log(binding.value.text) // => "привет!"
})
1
2
3
4

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

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

<my-component v-demo="test"></my-component>
1
app.component('my-component', {
  template: `
    <div> // директива v-demo будет добавлена на этот элемент
      <span>Содержимое этого компонента</span>
    </div>
  `
})
1
2
3
4
5
6
7

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

Deployed on Netlify.
Последнее обновление: 2020-12-08, 20:37:02 UTC