Библиотека Haunted использует концепцию реактивных компонентов на основе Web Components и хуков, аналогичных React, для управления состоянием и обновлениями интерфейса. Одной из ключевых задач в оптимизации производительности является батчинг обновлений, то есть группировка нескольких изменений состояния в один цикл перерисовки, чтобы минимизировать количество рефрешей DOM.
Каждый компонент Haunted обновляется при изменении
реактивного состояния, созданного с помощью хука
useState или useReducer. В стандартной работе
каждый вызов setState инициирует синхронный рендер
компонента, что может приводить к множественным перерисовкам
при последовательном изменении состояния.
Haunted решает эту проблему за счёт асинхронного планирования
обновлений. Компоненты регистрируют себя в очереди обновлений,
и библиотека использует микротаски
(Promise.resolve().then(...)) для объединения нескольких
изменений в один вызов update.
import { html, component, useState } from 'haunted';
function Counter() {
const [count, setCount] = useState(0);
const [step, setStep] = useState(1);
const increment = () => {
setCount(count + step);
setStep(step + 1);
};
return html`
<div>Count: ${count}</div>
<div>Step: ${step}</div>
<button @click=${increment}>Increment</button>
`;
}
customElements.define('my-counter', component(Counter));
В этом примере два состояния обновляются
одновременно через один обработчик. Благодаря внутреннему
батчингу Haunted DOM будет обновлён один раз, несмотря
на два вызова setState.
Если сравнивать с прямыми изменениями DOM или с обычными Web
Components без Haunted, каждое изменение innerHTML или
textContent выполняет отдельный рендер.
Это приводит к лишним перерасходам ресурсов при множественных
обновлениях. Батчинг позволяет:
Haunted использует две основные очереди:
update на следующем tick события JavaScript, что
гарантирует объединение всех синхронных вызовов
setState.Принцип работы можно представить как:
setState -> добавление компонента в локальную очередь -> планирование микротаска -> выполнение всех обновлений -> рендер компонентов
Хотя Haunted автоматически батчит обновления, иногда требуется
явное управление. Для этого можно использовать
component.update или функцию queueMicrotask
для планирования действий после всех текущих изменений.
setCount(count + 1);
queueMicrotask(() => {
console.log('Все обновления компонента завершены');
});
Этот подход полезен при:
Для больших и сложных компонентов батчинг становится критически важным. Рекомендуется:
setState внутри
циклов.setState(prev => ...)) для корректного объединения
нескольких изменений.setCount(prev => prev + 1);
setStep(prev => prev + 2);
Использование функциональных обновлений гарантирует, что каждое изменение будет корректно учтено в рамках одного батча.
Батчинг тесно связан с хуками Haunted:
useState и useReducer интегрированы с
внутренней очередью обновлений.useEffect выполняется после завершения всех батчей, что
позволяет безопасно работать с DOM после перерисовки.useRef остаётся стабильным между обновлениями, позволяя
хранить данные вне батча без лишних рендеров.function ComplexCounter() {
const [count, setCount] = useState(0);
const [step, setStep] = useState(1);
const [log, setLog] = useState([]);
const incrementAll = () => {
setCount(prev => prev + step);
setStep(prev => prev + 1);
setLog(prev => [...prev, `Count: ${count + step}, Step: ${step + 1}`]);
};
return html`
<div>Count: ${count}</div>
<div>Step: ${step}</div>
<button @click=${incrementAll}>Increment All</button>
<ul>${log.map(item => html`<li>${item}</li>`)}</ul>
`;
}
В этом случае три состояния обновляются одновременно, но Haunted гарантирует один рендер компонента, объединяя все изменения.
Батчинг обновлений является фундаментальной оптимизацией Haunted, позволяющей создавать производительные интерфейсы на базе Web Components с минимальными затратами на перерисовку DOM.