# Пользовательские директивы
# Введение
Кроме использования встроенных директив (таких как v-model
, v-show
), Vue позволяет создавать пользовательские. При этом важно понимать, что основным механизмом для создания повторно используемого кода во Vue всё-таки являются компоненты. Но для выполнения низкоуровневых операций с DOM директивы могут быть очень полезны. К примеру, для реализации фокуса на элементе input:
See the Pen Пользовательские директивы: простой пример 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 от {{ direction }} страницы</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 Пользовательские директивы: пример с динамическим аргументом 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 || 'top' }} страницы</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 Пользовательские директивы: динамический аргумент + динамическая привязка 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 — они могут принимать любые допустимые 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"
. А с поддержкой фрагментов у компонентов может быть более одного корневого элемента. При указании директивы на компоненте с несколькими корневыми элементами, директива будет проигнорирована и выведено предупреждение в консоль.