# Пользовательские директивы
# Введение
Кроме встроенных директив (таких как 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()
}
})
2
3
4
5
6
7
8
9
10
Чтобы зарегистрировать директиву локально, можно передать опцию directives
при определении компонента:
directives: {
focus: {
// определение директивы
mounted(el) {
el.focus()
}
}
}
2
3
4
5
6
7
8
После регистрации в шаблоне можно использовать новый атрибут v-focus
:
<input v-focus />
# Хуки
Для жизненного цикла директивы можно указать следующие хуки (все они опциональны):
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>
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')
2
3
4
5
6
7
8
9
10
11
Это закрепит элемент в 200px от начала страницы. Но что если возникнет случай, когда необходимо закрепить элемент слева, а не сверху? Для этого пригодится динамический аргумент директивы, который можно определить для каждого экземпляра компонента:
<div id="dynamicexample">
<h3>Прокрутите страницу вниз</h3>
<p v-pin:[direction]="200">Элемент зафиксирован в 200px слева от страницы.</p>
</div>
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')
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>
2
3
4
5
const app = Vue.createApp({
data() {
return {
direction: 'right',
pinPadding: 200
}
}
})
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'
}
})
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'
})
2
3
4
5
# Передача объекта данных в директиву
В случае, если директива должна принимать несколько параметров, можно указать объект JavaScript — годится любое валидное выражение, помните?
<div v-demo="{ color: 'белый', text: 'привет!' }"></div>
app.directive('demo', (el, binding) => {
console.log(binding.value.color) // => "белый"
console.log(binding.value.text) // => "привет!"
})
2
3
4
# Использование на компонентах
При использовании на компонентах пользовательская директива всегда будет применяться к корневому элементу компонента, аналогично передаче обычных атрибутов.
<my-component v-demo="test"></my-component>
app.component('my-component', {
template: `
<div> // директива v-demo будет добавлена на этот элемент
<span>Содержимое этого компонента</span>
</div>
`
})
2
3
4
5
6
7
В отличие от атрибутов, директивы не могут быть переданы другому элементу с помощью v-bind="$attrs"
. А с поддержкой фрагментов у компонента может быть более одного корневого элемента. При указании директивы на компоненте с несколькими корневыми элементами, директива будет проигнорирована и выдано предупреждение в консоли.