Гидратация компонентов

Haunted — это библиотека для создания веб-компонентов с реактивными свойствами, построенная поверх стандарта Web Components и использующая функциональный подход, похожий на React Hooks. Одной из ключевых возможностей является гидратация компонентов, позволяющая связывать уже существующий HTML с реактивной логикой без повторного рендеринга всей структуры DOM. Это особенно важно для серверного рендеринга (SSR), когда HTML генерируется на сервере, а на клиенте нужно “оживить” компоненты.

Что такое гидратация

Гидратация — это процесс, при котором существующие DOM-элементы получают связанную с ними реактивную функциональность. Вместо того чтобы удалять и заново создавать весь DOM, Haunted просто “подключает” свои хуки и состояния к уже отрендеренному содержимому. Это снижает нагрузку на браузер, ускоряет загрузку страницы и улучшает SEO.

В Haunted гидратация работает через:

  • customElements.define для регистрации компонентов.
  • useState, useEffect и другие хуки для реактивного поведения.
  • render для связывания DOM с функцией компонента.

Гидратация с 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

На сервере генерируется статический 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 с тем, что отрендерено на сервере.
  • Избегать динамически генерируемого HTML на сервере без точного соответствия клиентской логике.
  • Для данных, которые могут отличаться на клиенте, использовать эффекты с 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.
  • Для правильной гидратации все интерактивные элементы должны быть определены внутри функции компонента.

Советы по оптимизации гидратации

  1. Минимизировать объём статического HTML, чтобы ускорить первичную загрузку.
  2. Использовать уникальные идентификаторы элементов, если требуется выборка внутри компонента.
  3. Проверять совпадение состояния между сервером и клиентом.
  4. Разделять гидратацию на отдельные элементы, чтобы не гидратировать всю страницу сразу.

Гидратация в Haunted делает возможным эффективное соединение SSR и клиентской реактивной логики, позволяя создавать высокопроизводительные приложения с быстрым первым отображением и полноценной интерактивностью без лишних перерисовок.