Директивы Vue

Vue-директивы позволяют декларативно подключать timeago.js к DOM-элементам прямо в шаблоне, без написания компонентов.


Концепция директивы

Директива — это объект с хуками жизненного цикла: mounted, updated, unmounted. Директива для timeago.js вызывает render при монтировании и cancel при размонтировании.


Базовая директива v-timeago

// directives/timeago.js
import { render, cancel } from 'timeago.js';

export const vTimeago = {
  mounted(el, binding) {
    const locale = binding.value || binding.arg || 'ru';
    render(el, locale);
  },
  updated(el, binding) {
    const locale = binding.value || binding.arg || 'ru';
    cancel(el);
    render(el, locale);
  },
  unmounted(el) {
    cancel(el);
  },
};

Регистрация:

// main.js
import { createApp } from 'vue';
import App from './App.vue';
import { vTimeago } from './directives/timeago';

const app = createApp(App);
app.directive('timeago', vTimeago);
app.mount('#app');

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

<template>
  <!-- Локаль по умолчанию -->
  <time v-timeago datetime="2025-05-26T10:00:00Z" />

  <!-- Локаль через значение -->
  <time v-timeago="'ru'" datetime="2025-05-26T10:00:00Z" />

  <!-- Локаль через arg -->
  <time v-timeago:de datetime="2025-05-26T10:00:00Z" />
</template>

Директива с модификаторами

export const vTimeago = {
  mounted(el, binding) {
    const locale = binding.value || 'ru';

    // Модификатор .noUpdate — без автообновления
    if (binding.modifiers.noUpdate) {
      const { format } = require('timeago.js');
      el.textContent = format(el.getAttribute('datetime'), locale);
      return;
    }

    render(el, locale);

    // Модификатор .title — добавить абсолютное время в title
    if (binding.modifiers.title) {
      const date = el.getAttribute('datetime');
      el.title = new Intl.DateTimeFormat('ru', {
        dateStyle: 'medium',
        timeStyle: 'short',
      }).format(new Date(date));
    }
  },
  unmounted: (el) => cancel(el),
};

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

<time v-timeago.noUpdate="'ru'" datetime="2025-05-26T10:00:00Z" />
<time v-timeago.title="'ru'" datetime="2025-05-26T10:00:00Z" />

Директива с data-атрибутом для локали

export const vTimeago = {
  mounted(el) {
    const locale = el.dataset.locale || 'ru';
    render(el, locale);
  },
  unmounted: (el) => cancel(el),
};
<time v-timeago data-locale="de" datetime="2025-05-26T10:00:00Z" />

Глобальная директива с конфигурацией

// plugins/timeago.js
import { register, render, cancel } from 'timeago.js';
import ru from 'timeago.js/esm/lang/ru';

export const TimeagoPlugin = {
  install(app, options = {}) {
    const defaultLocale = options.locale || 'ru';

    // Регистрировать локали из опций
    if (options.locales) {
      Object.entries(options.locales).forEach(([key, fn]) => register(key, fn));
    } else {
      register('ru', ru);
    }

    app.directive('timeago', {
      mounted(el, binding) {
        const locale = binding.value || defaultLocale;
        render(el, locale);
      },
      updated(el, binding) {
        const locale = binding.value || defaultLocale;
        cancel(el);
        render(el, locale);
      },
      unmounted: (el) => cancel(el),
    });
  },
};

Подключение:

// main.js
import { createApp } from 'vue';
import App from './App.vue';
import { TimeagoPlugin } from './plugins/timeago';
import ru from 'timeago.js/esm/lang/ru';
import de from 'timeago.js/esm/lang/de';

const app = createApp(App);
app.use(TimeagoPlugin, {
  locale: 'ru',
  locales: { ru, de },
});
app.mount('#app');

Директива с реактивной локалью

import { watch } from 'vue';
import { render, cancel } from 'timeago.js';

export function createReactiveTimeago(localeRef) {
  return {
    mounted(el, binding) {
      render(el, localeRef.value);

      el._localeWatcher = watch(localeRef, (newLocale) => {
        cancel(el);
        render(el, newLocale);
      });
    },
    unmounted(el) {
      cancel(el);
      el._localeWatcher?.();
    },
  };
}

Пример применения директивы в списке

<template>
  <ul>
    <li v-for="post in posts" :key="post.id">
      <h3>{{ post.title }}</h3>
      <time v-timeago="locale" :datetime="post.createdAt" />
    </li>
  </ul>
</template>

<script setup>
import { ref } from 'vue';

const locale = ref('ru');

const posts = [
  { id: 1, title: 'Пост 1', createdAt: '2025-05-26T10:00:00Z' },
  { id: 2, title: 'Пост 2', createdAt: '2025-05-25T08:00:00Z' },
];
</script>