Haunted — это библиотека для создания веб-компонентов с реактивными свойствами, построенная поверх стандарта Web Components и использующая функциональный подход, похожий на React Hooks. Одной из ключевых возможностей является гидратация компонентов, позволяющая связывать уже существующий HTML с реактивной логикой без повторного рендеринга всей структуры DOM. Это особенно важно для серверного рендеринга (SSR), когда HTML генерируется на сервере, а на клиенте нужно “оживить” компоненты.
Гидратация — это процесс, при котором существующие DOM-элементы получают связанную с ними реактивную функциональность. Вместо того чтобы удалять и заново создавать весь DOM, Haunted просто “подключает” свои хуки и состояния к уже отрендеренному содержимому. Это снижает нагрузку на браузер, ускоряет загрузку страницы и улучшает SEO.
В Haunted гидратация работает через:
render и componentОбычная функция Haunted-компонента выглядит так:
import { html, component, useState } from 'haunted';
function Counter() {
const [count, setCount] = useState(0);
return html`
<div>
<p>Счетчик: ${count}</p>
<button @click=${() => setCount(count + 1)}>Увеличить</button>
</div>
`;
}
customElements.define('my-counter', component(Counter));
При обычном рендеринге render создаёт DOM с нуля. Для
гидратации необходимо уже существующий HTML, например с
сервера, и привязка к нему:
import { hydrate } from 'haunted';
const el = document.querySelector('my-counter');
hydrate(el, Counter);
Ключевой момент: hydrate не удаляет содержимое элемента, а вместо этого присваивает существующему DOM реактивную логику.
render и hydrate| Характеристика | render | hydrate |
|---|---|---|
| Создание DOM | Полное создание нового DOM | Использует существующий DOM |
| Потеря состояния | Да, при повторном рендере | Нет, сохраняется содержимое |
| Использование с SSR | Не оптимально | Идеально |
| Производительность | Средняя | Высокая |
На сервере генерируется статический HTML:
<my-counter>
<div>
<p>Счетчик: 5</p>
<button>Увеличить</button>
</div>
</my-counter>
На клиенте:
import { component, useState, hydrate } from 'haunted';
function Counter() {
const [count, setCount] = useState(5); // Начальное значение совпадает с серверным
return html`
<div>
<p>Счетчик: ${count}</p>
<button @click=${() => setCount(count + 1)}>Увеличить</button>
</div>
`;
}
const el = document.querySelector('my-counter');
hydrate(el, component(Counter));
В результате кнопка получает рабочий обработчик, счетчик становится реактивным, но HTML не перерисовывается заново.
Чтобы избежать рассогласования между серверным и клиентским HTML, важно:
useState с тем, что отрендерено на сервере.useEffect для корректной
синхронизации.Пример с асинхронными данными:
import { html, component, useState, useEffect, hydrate } from 'haunted';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/user/${userId}`)
.then(res => res.json())
.then(data => setUser(data));
}, [userId]);
if (!user) return html`<p>Загрузка...</p>`;
return html`<div>${user.name} (${user.email})</div>`;
}
const el = document.querySelector('user-profile');
hydrate(el, component(UserProfile));
Здесь HTML, созданный сервером, может содержать заглушку, а после гидратации компонент подхватывает асинхронные данные.
Haunted автоматически привязывает события, указанные через директиву
@ (например, @click). При гидратации эти
обработчики не удаляют существующий HTML, а добавляют
реактивность поверх него.
Важно помнить:
onclick в серверном
HTML, не перехватываются Haunted.Гидратация в Haunted делает возможным эффективное соединение SSR и клиентской реактивной логики, позволяя создавать высокопроизводительные приложения с быстрым первым отображением и полноценной интерактивностью без лишних перерисовок.