Корректное удаление обработчиков и таймеров — важная часть работы с
timeago.js. Без явной остановки таймеры render продолжают
работать, что ведёт к утечкам памяти и ошибкам в приложении.
При вызове render(nodes, locale) для каждого элемента
создаётся таймер через setTimeout. Этот таймер:
import { cancel } from 'timeago.js';
// Остановить все таймеры
cancel();
// Остановить для конкретного элемента
cancel(document.getElementById('time'));
// Остановить для коллекции
cancel(document.querySelectorAll('.time'));
При размонтировании компонента:
// Жизненный цикл компонента
function onMount(el) {
render(el, 'ru');
}
function onUnmount(el) {
cancel(el); // обязательно
}
При навигации в SPA:
router.beforeEach((to, from, next) => {
cancel(); // остановить все таймеры перед переходом
next();
});
При скрытии контента:
function hideModal() {
const times = modal.querySelectorAll('[datetime]');
cancel(times);
modal.style.display = 'none';
}
import { useEffect, useRef } from 'react';
import { render, cancel } from 'timeago.js';
function TimeAgo({ date }: { date: string }) {
const ref = useRef<HTMLTimeElement>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
render(el, 'ru');
// Cleanup function — вызывается при размонтировании
return () => {
cancel(el);
};
}, [date]);
return <time ref={ref} dateTime={date} />;
}
Функция, возвращаемая из useEffect, вызывается React
автоматически при:
[date]).<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { render, cancel } from 'timeago.js';
const props = defineProps({ date: String });
const el = ref(null);
onMounted(() => render(el.value, 'ru'));
onBeforeUnmount(() => cancel(el.value));
</script>
import { Component, OnInit, OnDestroy, ElementRef } from '@angular/core';
import { render, cancel } from 'timeago.js';
@Component({ template: '<time [attr.datetime]="date"></time>' })
export class TimeComponent implements OnInit, OnDestroy {
date = '2025-05-26T10:00:00Z';
constructor(private el: ElementRef) {}
ngOnInit() {
render(this.el.nativeElement.querySelector('time'), 'ru');
}
ngOnDestroy() {
cancel(this.el.nativeElement.querySelector('time'));
}
}
<script>
import { onMount, onDestroy } from 'svelte';
import { render, cancel } from 'timeago.js';
export let date;
let el;
onMount(() => render(el, 'ru'));
onDestroy(() => cancel(el));
</script>
<time bind:this={el} datetime={date}></time>
import { render, cancel } from 'timeago.js';
const observer = new IntersectionObserver((entries) => {
entries.forEach(({ target, isIntersecting }) => {
if (isIntersecting) {
render(target, 'ru');
} else {
cancel(target); // убрать таймер для невидимого элемента
}
});
});
document.querySelectorAll('.time').forEach(el => observer.observe(el));
import { cancel } from 'timeago.js';
const mo = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
mutation.removedNodes.forEach(node => {
if (node.nodeType !== Node.ELEMENT_NODE) return;
if (node.matches('[datetime]')) cancel(node);
node.querySelectorAll?.('[datetime]').forEach(cancel);
});
});
});
mo.observe(document.body, { childList: true, subtree: true });
import { cancel } from 'timeago.js';
window.addEventListener('beforeunload', () => {
cancel(); // освободить все таймеры
});
Если компонент с render размонтируется без
cancel:
Это классическая утечка памяти в SPA-приложениях.