Пользовательские директивы Vue на примере - отслеживание GA в приложении Vue

Эта статья представляет собой пошаговое руководство по созданию настраиваемой директивы в Vue. В качестве примера напишем рабочий плагин для отслеживания GA.

Плагин 🔌

Весь код отслеживания должен быть инкапсулирован в плагин, и внутри этого плагина будет определена директива. Назовем файл tracking.js.

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

const loadScript = (url) => {
  return new Promise((resolve, reject) => {
    const head = document.head || document.getElementsByTagName('head')[0]
    const script = document.createElement('script')
    script.async = true
    script.src = url

    head.appendChild(script)

    script.onload = resolve
    script.onerror = reject
  })
}
const install = async (Vue, { router }) => {
  await loadScript('https://www.google-analytics.com/analytics.js');
  ga('create', process.env.VUE_APP_GA_TRACKING, 'auto');
}

export default {
  install
}

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

Следующим шагом будет определение методов, которые мы будем использовать для отслеживания данных. Мы хотим отслеживать как события, так и просмотры страниц.

const track = ({category, action, label}) => {
  if (typeof ga === 'function') {
    ga('send', 'event', category, action, label);
  }
}

const trackPageView = () => {
  if (typeof ga === 'function') {
    ga('send', 'pageview');
  }
}

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

После этих изменений способ установки должен выглядеть следующим образом:

const install = async (Vue, { router }) => {
  Vue.prototype.$gaTrack = (payload) => {
    track(payload)
  };

  router.afterEach(() => {
    trackPageView();
  });

  await loadScript('https://www.google-analytics.com/analytics.js');
  ga('create', process.env.VUE_APP_GA_TRACKING, 'auto');
}

Теперь мы можем вызывать наши компоненты Vue this.$gaTrack(...) для отслеживания данных в GA.

В приведенном выше решении есть один поток. Мы можем вызвать один из методов отслеживания до загрузки кода GA, загрузка скрипта асинхронная, что может привести к потере данных. Решение этой проблемы - это так называемая ленивая очередь. Всего несколько строк кода, но он отлично решает подобные проблемы.

const lazyQueue = () => {
  const events = [];
  let started = false;

  return {
    push: (event) => {
      if (started) event();
      else events.push(event);
    },
    start: () => {
      started = true;

      while (events.length >= 1) {
        events.shift()();
      }
    }
  };
}

lazyQueue предоставляет два метода: push и start. Мы можем отправить события, которые являются обратными вызовами, в очередь, и они будут взяты из очереди и выполнены только после того, как очередь была запущена. Имея очередь, нам нужно обновить наш install метод.

const install = async (Vue, { router }) => {
  const queue = lazyQueue();

  Vue.prototype.$gaTrack = (payload) => {
    queue.push(() => track(payload))
  };

  router.afterEach(() => {
    queue.push(trackPageView);
  });

  await loadScript('https://www.google-analytics.com/analytics.js');
  ga('create', process.env.VUE_APP_GA_TRACKING, 'auto');
  queue.start();
}

Первое, что мы хотим сделать в install методе, - это запустить lazyQueue. Методы отслеживания не вызывают напрямую track, но вместо этого помещают событие в очередь. После загрузки скрипта аналитики и инициализации трекера мы запускаем очередь.

Теперь плагин готов к использованию, следующий шаг, директива.

Директива 📃

Имея директиву, мы должны иметь возможность делать что-то вроде этого в компоненте Vue:

<h1
  v-track:click="{category: 'title', action: 'click', label: 'home'}"
>
  {{ msg }}
</h1>

Элемент, к которому мы добавляем директиву, может быть либо узлом HTML, либо другим компонентом Vue. Более того, директива должна работать как для собственных, так и для настраиваемых событий.

Пользовательские директивы Vue предоставляют несколько функций-перехватчиков:

  • связывать
  • развязать
  • вставлен
  • Обновить
  • componentUpdated

Каждый хук вызывается в разный момент, подробное объяснение - в документации.

В нашем примере мы будем использовать два: bind и unbind. Bind вызывается только один раз, когда директива привязана к элементу. С другой стороны, unbind вызывается, когда директива не привязана к элементу. Эти два идеальны, потому что нам нужно добавить, а затем удалить прослушиватели событий, так что это одноразовая работа.

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

Учитывая, что директива вызывается так:
v-track:click=”{category: ‘title’, action: ‘click’, label: ‘home’}”
нам нужно свойство arg из контекста привязки, которое является событием (click), и value, которое является значением, которое мы хотим отслеживать.

Поскольку мы хотим добавить прослушиватель событий к элементу, нам нужно получить этот элемент. Хотя есть два сценария, которые следует учитывать:

  1. директива привязана к обычному узлу HTML,
  2. директива привязана к компоненту Vue.

Если директива привязана к обычному узлу HTML, нам нужен первый аргумент. Затем мы можем прикрепить к этому элементу прослушиватель событий для события, которое было передано как arg, вызвав метод attachEventListener для переданного элемента.

Если директива привязана к компоненту Vue, нам нужен третий аргумент. Вместо вызова attachEventListener для элемента мы можем использовать API компонента и вызвать метод $on для vnode.

Объявление директивы выглядит следующим образом:

const listeners = {}

  Vue.directive('track', {
    bind(el, { arg: event, value }, vnode) {
      if (vnode.componentInstance) {
        vnode.componentInstance.$on(event, () => {
          track(value);
        });
      } else {
        listeners[el] = () => {
          track(value);
        }
        el.addEventListener(event, listeners[el])
      }
    },
    unbind(el, { arg: event }, vnode) {
      if (vnode.componentInstance) {
        vnode.componentInstance.$off(event);
      } else {
        el.removeEventListener(event, listeners[el])
      }
    }
  });

Полный плагин:

Ресурсы