Создание пользовательских директив

Директивы во Vue.js — это специальные атрибуты, которые добавляют поведение к DOM-элементам. Помимо встроенных директив (v-if, v-for, v-model и др.), Vue позволяет создавать собственные пользовательские директивы для повторного использования логики взаимодействия с DOM.

Регистрация пользовательской директивы

Пользовательская директива регистрируется глобально или локально.

Глобальная регистрация:

import { createApp } from 'vue';
import App from './App.vue';

const app = createApp(App);

app.directive('focus', {
  mounted(el) {
    el.focus();
  }
});

app.mount('#app');

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

Локальная регистрация:

export default {
  directives: {
    highlight: {
      beforeMount(el, binding) {
        el.style.backgroundColor = binding.value || 'yellow';
      }
    }
  }
};

Локальная директива доступна только внутри конкретного компонента.

Жизненный цикл директивы

Пользовательские директивы во Vue имеют набор хуков, аналогичный жизненному циклу компонентов:

  • beforeMount(el, binding, vnode, prevVnode) – вызывается до монтирования элемента.
  • mounted(el, binding, vnode, prevVnode) – вызывается после монтирования элемента.
  • beforeUpdate(el, binding, vnode, prevVnode) – вызывается перед обновлением VNode.
  • updated(el, binding, vnode, prevVnode) – вызывается после обновления VNode.
  • beforeUnmount(el, binding, vnode, prevVnode) – вызывается перед удалением элемента.
  • unmounted(el, binding, vnode, prevVnode) – вызывается после удаления элемента.

Параметр binding содержит важные свойства:

  • binding.value — значение, переданное директиве.
  • binding.oldValue — предыдущее значение.
  • binding.arg — аргумент директивы (например, в v-my-directive:arg будет arg).
  • binding.modifiers — объект модификаторов, например { lazy: true } для v-my-directive.lazy.

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

Изменение стиля элемента:

app.directive('color', {
  beforeMount(el, binding) {
    el.style.color = binding.value || 'red';
  },
  updated(el, binding) {
    el.style.color = binding.value || 'red';
  }
});

Использование в шаблоне:

<p v-color="'blue'">Текст с синим цветом</p>

Динамическая обработка событий:

app.directive('click-outside', {
  mounted(el, binding) {
    el.__clickOutsideHandler__ = (event) => {
      if (!el.contains(event.target)) {
        binding.value(event);
      }
    };
    document.addEventListener('click', el.__clickOutsideHandler__);
  },
  unmounted(el) {
    document.removeEventListener('click', el.__clickOutsideHandler__);
    delete el.__clickOutsideHandler__;
  }
});

Применение:

<div v-click-outside="handleClickOutside">Содержимое</div>

В этом примере директива реагирует на клики за пределами элемента, вызывая функцию handleClickOutside.

Модификаторы и аргументы

Директивы могут использовать модификаторы для расширения функционала:

app.directive('scroll', {
  mounted(el, binding) {
    const beh * avior = binding.modifiers.smooth ? 'smooth' : 'auto';
    el.addEventListener('scroll', () => {
      console.log('Скролл с поведением', behavior);
    });
  }
});

Использование:

<div v-scroll.smooth>Прокручиваемый элемент</div>

Аргументы позволяют передавать дополнительную информацию:

app.directive('style', {
  mounted(el, binding) {
    el.style[binding.arg] = binding.value;
  }
});

Пример в шаблоне:

<p v-style:background="'lightblue'">Фон элемента изменен</p>

Практические рекомендации

  • Для логики, связанной с DOM, предпочтительно использовать директивы, а не методы или вычисляемые свойства.
  • Для повторно используемой логики лучше создавать глобальные директивы.
  • Не хранить внутри директив сложное состояние компонентов — директива должна ограничиваться манипуляциями DOM.
  • Всегда очищать ресурсы (например, слушатели событий) в хуке unmounted, чтобы избежать утечек памяти.

Итоговый шаблон пользовательской директивы

app.directive('my-directive', {
  beforeMount(el, binding, vnode, prevVnode) {
    // Инициализация перед монтированием
  },
  mounted(el, binding, vnode, prevVnode) {
    // Логика после монтирования
  },
  beforeUpdate(el, binding, vnode, prevVnode) {
    // Подготовка к обновлению
  },
  updated(el, binding, vnode, prevVnode) {
    // Обновление после изменения
  },
  beforeUnmount(el, binding, vnode, prevVnode) {
    // Очистка перед удалением
  },
  unmounted(el, binding, vnode, prevVnode) {
    // Финальная очистка
  }
});

Такой подход обеспечивает полное управление жизненным циклом DOM-элементов и гибкость при создании сложного интерфейса. Пользовательские директивы остаются важным инструментом для повышения повторного использования кода и упрощения работы с DOM в Vue.js.