Внешние менеджеры состояния

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


Основные принципы работы с внешними менеджерами состояния

Внешний менеджер состояния — это централизованное хранилище данных приложения, доступное нескольким компонентам одновременно. Оно отделяет логику управления состоянием от визуальной части, что повышает масштабируемость и тестируемость кода.

Ключевые задачи внешнего менеджера:

  • Хранение глобального состояния приложения.
  • Обеспечение реактивного обновления компонентов при изменении состояния.
  • Поддержка механизмов подписки для синхронизации данных.
  • Интеграция с асинхронными операциями и побочными эффектами.

В Haunted можно использовать любой внешний менеджер, совместимый с JavaScript, включая Redux, Zustand, MobX, RxJS и другие. Главное — обеспечить реактивную подписку компонентов на изменения состояния.


Интеграция Haunted с Redux

Redux предоставляет единый источник правды и строгий подход к мутациям состояния через чистые функции-редьюсеры.

Создание хранилища Redux:

import { createStore } from 'redux';

const initialState = { count: 0 };

function reducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
}

export const store = createStore(reducer);

Подключение к Haunted-компоненту:

import { html, component, useState, useEffect } from 'haunted';
import { store } from './store.js';

function Counter() {
  const [, setTick] = useState(0);

  useEffect(() => {
    const unsubscribe = store.subscribe(() => setTick(tick => tick + 1));
    return unsubscribe;
  }, []);

  const state = store.getState();

  return html`
    <div>
      <p>Count: ${state.count}</p>
      <button @click=${() => store.dispatch({ type: 'INCREMENT' })}>+</button>
      <button @click=${() => store.dispatch({ type: 'DECREMENT' })}>-</button>
    </div>
  `;
}

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

Особенности подхода:

  • Используется локальный useState только для форсирования ререндеринга.
  • Подписка на store позволяет автоматически обновлять компонент при изменении состояния.
  • События кнопок диспатчат действия в Redux.

Использование Zustand

Zustand — это минималистичный и реактивный менеджер состояния с простым API. Он хорошо сочетается с Haunted благодаря прямой поддержке подписок.

Создание состояния:

import create from 'zustand';

export const useStore = create(set => ({
  count: 0,
  increment: () => set(state => ({ count: state.count + 1 })),
  decrement: () => set(state => ({ count: state.count - 1 })),
}));

Использование в Haunted-компоненте:

import { html, component } from 'haunted';
import { useStore } from './store.js';

function Counter() {
  const count = useStore(state => state.count);
  const increment = useStore(state => state.increment);
  const decrement = useStore(state => state.decrement);

  return html`
    <div>
      <p>Count: ${count}</p>
      <button @click=${increment}>+</button>
      <button @click=${decrement}>-</button>
    </div>
  `;
}

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

Преимущества подхода:

  • Нет необходимости вручную форсировать ререндер.
  • Состояние компонента всегда синхронизировано с внешним стором.
  • API лаконичный, легко расширяется.

Подписки и реактивность

В Haunted можно использовать useEffect для интеграции с любым менеджером состояния, поддерживающим подписки. Ключевая задача — вызвать обновление компонента при изменении состояния. Пример:

useEffect(() => {
  const unsubscribe = externalStore.subscribe(() => {
    setTick(tick => tick + 1);
  });
  return unsubscribe;
}, []);

В этом случае компонент будет реагировать на изменения глобального состояния, не храня его локально.


Асинхронные операции и побочные эффекты

Многие внешние менеджеры состояния предоставляют встроенные механизмы для асинхронных операций:

  • Redux: middleware (redux-thunk, redux-saga) для обработки промисов и эффектов.
  • Zustand: встроенная возможность обновления состояния через асинхронные функции.
  • RxJS: потоковые данные с подпиской и оператором subscribe.

Пример асинхронного увеличения счетчика через Zustand:

incrementAsync: async () => {
  await new Promise(resolve => setTimeout(resolve, 1000));
  set(state => ({ count: state.count + 1 }));
}

Лучшие практики при использовании внешних менеджеров состояния с Haunted

  • Минимизировать локальное состояние: использовать useState только для состояния, относящегося к конкретному компоненту.
  • Разделять логику и представление: компоненты должны получать состояние через подписку, а не напрямую управлять им.
  • Использовать мемоизацию: при работе с селекторами состояния избегать лишних ререндеров.
  • Обеспечивать чистые эффекты: useEffect должен корректно отписываться при размонтировании компонентов.

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