Рекомендации по оптимизации

Набор практических рекомендаций для эффективного использования библиотеки в production-приложениях.


1. Предпочитать format для статичных списков

Если список постов не обновляется в реальном времени, format эффективнее render:

// Хорошо для статичных списков
posts.map(post => ({
  ...post,
  timeLabel: format(post.createdAt, 'ru'),
}));

render создаёт таймеры. Для неизменного контента это лишние ресурсы.


2. Всегда вызывать cancel при размонтировании

useEffect(() => {
  render(ref.current, 'ru');
  return () => cancel(ref.current); // обязательно
}, []);

Без cancel накапливаются зависшие таймеры.


3. Вызывать cancel перед повторным render

cancel(nodes);
render(nodes, newLocale);

Повторный render без cancel удваивает количество таймеров.


4. Использовать IntersectionObserver

Отключать таймеры для невидимых элементов:

const io = new IntersectionObserver((entries) => {
  entries.forEach(({ target, isIntersecting }) => {
    isIntersecting ? render(target, 'ru') : cancel(target);
  });
});

nodes.forEach(n => io.observe(n));

5. Ограничить количество одновременно обновляемых элементов

const MAX = 100;
const nodes = Array.from(document.querySelectorAll('[datetime]')).slice(0, MAX);
render(nodes, 'ru');

6. Для списков > 500 элементов — виртуализация

react-window, react-virtuoso, vue-virtual-scroller решают проблему на уровне архитектуры. timeago обновляет только видимые элементы по умолчанию.


7. Проверять значение перед записью

const next = format(date, 'ru');
if (el.textContent !== next) el.textContent = next;

Предотвращает ненужные reflow при неизменившемся значении.


8. Батчинг через requestAnimationFrame

function batchUpdate(elements) {
  requestAnimationFrame(() => {
    elements.forEach(el => {
      el.textContent = format(el.getAttribute('datetime'), 'ru');
    });
  });
}

9. Замораживать очень старые даты

function frozenRender(el, locale = 'ru') {
  const age = Date.now() - new Date(el.getAttribute('datetime')).getTime();
  if (age > 365 * 86400000) {
    el.textContent = format(el.getAttribute('datetime'), locale);
  } else {
    render(el, locale);
  }
}

Для дат старше года значение “1 год назад” → “2 года назад” изменится только через год. Незачем обновлять каждый день.


10. Нормализовать строки дат

function normalizeDate(str) {
  if (!str.endsWith('Z') && !str.includes('+')) {
    return str + 'Z';
  }
  return str;
}

Предотвращает неоднозначность при парсинге и потенциальные ошибки в расчёте разницы.


11. Регистрировать локали один раз при старте

// Только в точке входа
register('ru', ru);
register('de', de);

Повторная регистрация в компонентах — лишняя работа.


12. SSR: использовать только format

На сервере не вызывать render — нет DOM. Использовать format для начального рендера:

const timeLabel = format(post.createdAt, 'ru');
// Передать в шаблон

На клиенте при гидрации подключить render через useEffect / onMounted.


13. Ленивая загрузка локалей

async function loadLocale(locale) {
  const mod = await import(`timeago.js/esm/lang/${locale}.js`);
  register(locale, mod.default);
}

Не включать все локали в основной бандл.


14. Обновлять при возвращении на вкладку

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'visible') {
    cancel(nodes);
    render(nodes, 'ru');
  }
});

При длительном отсутствии пользователя таймеры мигут пропустить обновления.


15. Debounce при пакетных изменениях DOM

const refresh = debounce(() => {
  cancel(nodes);
  render(nodes, 'ru');
}, 200);

Предотвращает многократный перезапуск за короткое время при загрузке данных.


Итог: приоритеты оптимизации

  1. cancel при размонтировании — критично.
  2. Проверка перед записью — просто и эффективно.
  3. IntersectionObserver — при 100+ элементах.
  4. Виртуализация — при 500+ элементах.
  5. Заморозка старых дат — при наличии старого контента.