Haunted — это легковесная библиотека для создания веб-компонентов на основе функционального подхода, вдохновлённого React Hooks. Одной из ключевых задач при работе с Haunted является эффективное управление ререндерингом, поскольку избыточные обновления компонентов могут негативно влиять на производительность, особенно в сложных интерфейсах.
Ререндеринг в Haunted происходит, когда изменяются реактивные состояния или свойства компонента. Основные источники изменений:
useState и useReducer — при вызове функции
обновления состояния компонент повторно отрисовывается.useContext — изменение контекста инициирует обновление
всех зависимых компонентов.Важно понимать, что Haunted не использует виртуальный DOM, поэтому каждый ререндер создаёт новые узлы и пересоздаёт шаблон, что делает оптимизацию особенно критичной.
useMemo и useCallback позволяют сохранить
вычисленные значения и функции между ререндерами, предотвращая их
повторное создание:
import { html, component, useState, useMemo } from 'haunted';
function ExpensiveComponent({ items }) {
const processedItems = useMemo(() => {
return items.map(item => heavyComputation(item));
}, [items]);
return html`<ul>${processedItems.map(i => html`<li>${i}</li>`)}</ul>`;
}
customElements.define('expensive-component', component(ExpensiveComponent));
Здесь processedItems пересчитывается только при
изменении массива items, а не при каждом обновлении
компонента.
Часто ошибки производительности возникают, когда несколько несвязанных состояний хранятся вместе, вызывая ненужный ререндер всех зависимых частей. Следует разделять состояния:
const [count, setCount] = useState(0);
const [text, setText] = useState('');
Теперь изменение text не будет трогать визуализацию
счётчика.
shouldUpdate в кастомных
элементахHaunted позволяет контролировать ререндер через метод
shouldUpdate:
function MyComponent({ value }) {
this.shouldUpdate = (prevProps, nextProps) => prevProps.value !== nextProps.value;
return html`<p>${value}</p>`;
}
customElements.define('my-component', component(MyComponent));
Компонент перерисуется только при изменении конкретного свойства.
При рендеринге списков желательно использовать стабильные ключи, чтобы Haunted корректно сопоставлял DOM-элементы:
const items = [{ id: 1, name: 'A' }, { id: 2, name: 'B' }];
html`<ul>${items.map(item => html`<li key=${item.id}>${item.name}</li>`)}</ul>`;
Без ключей каждый элемент списка будет пересоздаваться заново при любом изменении массива.
Создание функций прямо в шаблоне приводит к их пересозданию на каждом
ререндере. Лучше использовать useCallback или отдельные
методы:
const handleClick = useCallback(() => console.log('clicked'), []);
html`<button @click=${handleClick}>Click</button>`;
Для больших компонентов имеет смысл разделять интерфейс на мелкие подкомпоненты, которые будут рендериться независимо. Это уменьшает область изменений и снижает нагрузку на браузер.
function Parent({ items }) {
return html`
<ul>
${items.map(item => html`<child-item .data=${item}></child-item>`)}
</ul>
`;
}
Каждый <child-item> ререндерится только при
изменении своего собственного состояния или свойства
data.
Haunted привязывает обработчики через шаблон. Следует избегать
чрезмерного вызова setState внутри событий
без необходимости:
button.addEventListener('input', () => setValue(e.target.value));
Если обновление состояния не требуется каждый раз, лучше использовать дебаунс или проверку на изменение значения.
Для анализа ререндеров можно использовать инструменты разработчика:
useEffect или
useLayoutEffect для контроля частоты обновлений.Регулярное профилирование позволяет выявлять узкие места, особенно в компонентах с большим количеством вложенных элементов или сложной логикой.
shouldUpdate при
необходимости.Эти подходы помогают уменьшить количество лишних обновлений, ускорить работу приложения и снизить нагрузку на браузер, сохраняя отзывчивость интерфейса даже при больших и динамичных данных.