Синхронизация состояния

Haunted — это библиотека для создания веб-компонентов на базе функций, вдохновленных React Hooks, с полной поддержкой реактивного состояния. Одним из ключевых аспектов эффективного использования Haunted является правильная организация и синхронизация состояния компонентов. Этот процесс включает управление локальным состоянием, передачу данных между компонентами и реакцию на внешние изменения.


Локальное состояние компонента

В Haunted локальное состояние определяется с помощью хука useState. Этот хук возвращает текущее значение состояния и функцию для его обновления. Обновление состояния автоматически инициирует повторный рендер компонента.

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));

Особенности локального состояния:

  • Каждое состояние привязано к конкретному экземпляру компонента.
  • Изменения состояния безопасны и изолированы между компонентами.
  • useState может принимать как примитивные значения, так и объекты. При работе с объектами необходимо создавать новые копии для корректной работы рендеринга.

Производные состояния и useEffect

Для синхронизации состояния с побочными эффектами используется useEffect. Этот хук позволяет реагировать на изменения зависимостей и выполнять действия, например, обновление DOM, запросы к API или таймеры.

import { html, component, useState, useEffect } from 'haunted';

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => setSeconds(s => s + 1), 1000);
    return () => clearInterval(interval);
  }, []);

  return html`<p>Прошло секунд: ${seconds}</p>`;
}

customElements.define('my-timer', component(Timer));

Ключевые моменты:

  • useEffect выполняется после рендера, что предотвращает блокировку UI.
  • Возвращаемая функция используется для очистки ресурсов, например, таймеров или подписок.
  • Второй аргумент — массив зависимостей — определяет, когда эффект должен повторно сработать. Пустой массив [] означает однократный вызов при монтировании.

Совместное использование состояний

Haunted поддерживает организацию состояния на более высоком уровне с помощью кастомных хуков и реактивных объектов. Для сложных компонентов рекомендуется создавать собственные хуки для управления логикой и синхронизации между компонентами.

import { useState, useEffect } from 'haunted';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(json => {
        setData(json);
        setLoading(false);
      });
  }, [url]);

  return [data, loading];
}

Использование кастомных хуков:

import { html, component } from 'haunted';
import { useFetch } from './hooks.js';

function DataViewer({ url }) {
  const [data, loading] = useFetch(url);

  return html`
    ${loading ? html`<p>Загрузка...</p>` : html`<pre>${JSON.stringify(data, null, 2)}</pre>`}
  `;
}

customElements.define('data-viewer', component(DataViewer));

Преимущества:

  • Повторное использование логики без дублирования.
  • Разделение состояния и отображения.
  • Упрощение синхронизации между несколькими компонентами.

Состояние и свойства компонента

Состояние компонентов можно синхронизировать с их свойствами (props). Свойства — это внешний способ передавать данные компоненту, а useState позволяет реагировать на их изменения.

import { html, component, useState, useEffect } from 'haunted';

function Greeting({ name }) {
  const [displayName, setDisplayName] = useState(name);

  useEffect(() => {
    setDisplayName(name);
  }, [name]);

  return html`<p>Привет, ${displayName}!</p>`;
}

customElements.define('my-greeting', component(Greeting));

Особенности синхронизации с props:

  • Использование useEffect для отслеживания изменений свойств.
  • Возможность создавать производные состояния на основе входных данных.
  • Поддержка реактивного обновления UI без прямой модификации props.

Глобальное состояние и контекст

Для синхронизации состояния между множеством компонентов можно использовать паттерн глобального состояния через контексты. Haunted не имеет встроенного контекста, но его можно реализовать через кастомные события или сторонние библиотеки, например, lit или redux.

Пример через кастомное событие:

// Эмиттер состояния
const state = { count: 0 };

function increment() {
  state.count++;
  window.dispatchEvent(new CustomEvent('state-change', { detail: state.count }));
}

// Подписка в компоненте
function Counter() {
  const [count, setCount] = useState(state.count);

  useEffect(() => {
    const handler = (e) => setCount(e.detail);
    window.addEventListener('state-change', handler);
    return () => window.removeEventListener('state-change', handler);
  }, []);

  return html`<button @click=${increment}>Счёт: ${count}</button>`;
}

customElements.define('global-counter', component(Counter));

Выводы по глобальному состоянию:

  • Эффективно для небольших приложений без сторонних библиотек.
  • Требует аккуратного управления событиями для предотвращения утечек памяти.
  • Позволяет синхронизировать состояние между компонентами независимо от их иерархии.

Практические рекомендации

  • Использовать useState для локальных значений и useEffect для их синхронизации с внешними источниками.
  • Разделять логику и отображение через кастомные хуки.
  • При работе с объектами или массивами создавать новые копии при обновлении состояния.
  • Для сложной архитектуры применять глобальное состояние с осторожной очисткой подписок.
  • Свойства компонента использовать как источник данных, но никогда не изменять их напрямую.

Синхронизация состояния в Haunted обеспечивает реактивность и предсказуемость поведения компонентов, делая архитектуру приложения более чистой и модульной.