Пакетная (batch) обработка обновлений снижает количество операций с DOM и уменьшает количество reflow-циклов браузера.
Если обновлять каждый элемент независимо:
elements.forEach(el => {
el.textContent = format(el.getAttribute('datetime'), 'ru');
});
Это вызывает N последовательных операций записи в DOM. Браузер может выполнять Recalculate Style после каждой записи (если читается геометрия между записями).
import { format } from 'timeago.js';
function batchUpdate(elements, locale = 'ru') {
requestAnimationFrame(() => {
elements.forEach(el => {
const date = el.getAttribute('datetime');
if (!date) return;
const newText = format(date, locale);
if (el.textContent !== newText) {
el.textContent = newText;
}
});
});
}
requestAnimationFrame гарантирует выполнение обновлений
перед следующим кадром браузера — в единой операции.
import { format } from 'timeago.js';
function addPosts(posts, container) {
const fragment = document.createDocumentFragment();
posts.forEach(post => {
const li = document.createElement('li');
li.innerHTML = `
<h3>${post.title}</h3>
<time datetime="${post.createdAt}">
${format(post.createdAt, 'ru')}
</time>
`;
fragment.appendChild(li);
});
// Одна операция добавления в DOM вместо N
container.appendChild(fragment);
}
import { render } from 'timeago.js';
function initBatch(selector = '[datetime]', locale = 'ru', batchSize = 50) {
const nodes = Array.from(document.querySelectorAll(selector));
let index = 0;
function processBatch() {
const batch = nodes.slice(index, index + batchSize);
render(batch, locale);
index += batchSize;
if (index < nodes.length) {
requestAnimationFrame(processBatch);
}
}
requestAnimationFrame(processBatch);
}
Инициализация 50 элементов за кадр — UI не блокируется при большом количестве.
Вместо отдельного таймера на каждый элемент — один общий:
import { format } from 'timeago.js';
function createBatchRenderer(locale = 'ru') {
let elements = new Set();
let timer;
function getMinInterval() {
let min = Infinity;
elements.forEach(el => {
const diff = Math.abs(Date.now() - new Date(el.getAttribute('datetime')).getTime());
let interval;
if (diff < 60000) interval = 10000;
else if (diff < 3600000) interval = 60000;
else if (diff < 86400000) interval = 1800000;
else interval = 86400000;
min = Math.min(min, interval);
});
return min === Infinity ? 60000 : min;
}
function tick() {
requestAnimationFrame(() => {
elements.forEach(el => {
const date = el.getAttribute('datetime');
if (!date) return;
const next = format(date, locale);
if (el.textContent !== next) el.textContent = next;
});
});
timer = setTimeout(tick, getMinInterval());
}
return {
add(el) {
elements.add(el);
if (elements.size === 1) tick();
},
remove(el) {
elements.delete(el);
if (elements.size === 0) clearTimeout(timer);
},
destroy() {
clearTimeout(timer);
elements.clear();
}
};
}
import { format } from 'timeago.js';
async function loadAndRenderPosts(page) {
const posts = await fetch(`/api/posts?page=${page}`).then(r => r.json());
const container = document.getElementById('feed');
const fragment = document.createDocumentFragment();
// Сначала создать все элементы (без DOM)
posts.forEach(post => {
const li = document.createElement('li');
li.innerHTML = `
<h3>${post.title}</h3>
<time datetime="${post.createdAt}">
${format(post.createdAt, 'ru')}
</time>
`;
fragment.appendChild(li);
});
// Одна вставка в DOM
container.appendChild(fragment);
// Подключить автообновление для новых элементов
const newTimes = container.querySelectorAll('li:last-child time, li:nth-last-child(-n+' + posts.length + ') time');
// Более надёжный способ — через добавленный fragment
}
import { cancel } from 'timeago.js';
function cancelAll(selector = '[datetime]') {
const nodes = document.querySelectorAll(selector);
cancel(nodes); // одна операция отмены для всех
}
При добавлении множества элементов в React используется автоматический батчинг (React 18+):
import { startTransition } from 'react';
function addManyPosts(posts) {
startTransition(() => {
// React батчит все setState-вызовы внутри
setPosts(prev => [...prev, ...posts]);
});
}
Состояние обновится одним ре-рендером, что снижает количество перерисовок.