Множественные контексты

Haunted — это библиотека для создания реактивных веб-компонентов на основе стандартов Web Components с использованием хуков, вдохновленных React. Одной из ключевых особенностей является поддержка контекстов, позволяющая компонентам взаимодействовать без необходимости явно передавать пропсы через несколько уровней вложенности. Работа с множественными контекстами требует особого внимания к структуре и порядку объявления провайдеров и потребителей контекста.


Создание контекста

Контекст в Haunted создаётся с помощью функции createContext. Результат — объект с двумя основными свойствами: Provider и Consumer.

import { createContext } from 'haunted';

const ThemeContext = createContext('light');
const UserContext = createContext({ name: 'Guest', role: 'visitor' });
  • ThemeContext задаёт тему интерфейса по умолчанию light.
  • UserContext хранит данные о пользователе.

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


Провайдеры для нескольких контекстов

Каждый контекст должен иметь свой <Provider>. Провайдер передаёт значение вниз по дереву компонентов. При множественных контекстах провайдеры можно вложить, и каждый компонент сможет подписаться на нужный ему контекст.

html`
  <${ThemeContext.Provider} value="dark">
    <${UserContext.Provider} value=${{ name: 'Alice', role: 'admin' }}>
      <app-content></app-content>
    <//>
  <//>
`
  • Порядок вложения имеет значение только для визуальной иерархии, не влияет на доступность контекста для компонентов, если они находятся внутри обоих провайдеров.
  • Каждый провайдер управляет только своим контекстом; значения разных контекстов не пересекаются.

Потребление нескольких контекстов

Для получения значений контекста используется хук useContext. Если компонент зависит сразу от нескольких контекстов, можно вызвать useContext несколько раз.

import { component, useContext, html } from 'haunted';

function AppContent() {
  const theme = useContext(ThemeContext);
  const user = useContext(UserContext);

  return html`
    <div class=${theme}>
      <p>Пользователь: ${user.name}</p>
      <p>Роль: ${user.role}</p>
    </div>
  `;
}

customElements.define('app-content', component(AppContent));
  • Вызов useContext повторно безопасен и не влияет на производительность, если используется корректно.
  • Значения контекста реактивны: при изменении value в провайдере компонент автоматически перерисовывается.

Обновление значений в нескольких контекстах

Изменение значения контекста происходит через изменение value в провайдере. Для динамических данных удобно использовать реактивные состояния (useState) или вычисляемые значения (useReducer, useMemo).

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

function ThemeSwitcher() {
  const [theme, setTheme] = useState('light');

  return html`
    <${ThemeContext.Provider} .value=${theme}>
      <button @click=${() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Переключить тему
      </button>
      <app-content></app-content>
    <//>
  `;
}

customElements.define('theme-switcher', component(ThemeSwitcher));
  • Контекст реактивен на любые изменения в value.
  • Можно обновлять несколько контекстов одновременно, но каждый Provider управляет только своим состоянием.

Советы по работе с множественными контекстами

  1. Разделять контексты по назначению — не использовать один контекст для хранения множества разнородных данных.
  2. Явное именование провайдеров и контекстов облегчает чтение и поддержку кода.
  3. Минимизировать вложенность — слишком глубокое вложение провайдеров усложняет отладку.
  4. Использовать локальные провайдеры при необходимости переопределить значение для части дерева компонентов.
  5. Следить за производительностью: слишком частые обновления контекста могут вызывать множественные перерисовки дочерних компонентов.

Комбинация контекстов с хуками состояния

Haunted позволяет сочетать множественные контексты с другими хуками, создавая мощные реактивные структуры:

function Dashboard() {
  const theme = useContext(ThemeContext);
  const user = useContext(UserContext);
  const [notifications, setNotifications] = useState([]);

  return html`
    <div class=${theme}>
      <h2>Привет, ${user.name}</h2>
      <ul>
        ${notifications.map(n => html`<li>${n}</li>`)}
      </ul>
      <button @click=${() => setNotifications([...notifications, 'Новое сообщение'])}>
        Добавить уведомление
      </button>
    </div>
  `;
}
  • Контексты работают параллельно с локальным состоянием компонента.
  • Компоненты автоматически реагируют на любые изменения контекста или локального состояния без дополнительного кода для подписки.

Итоговая структура для нескольких контекстов

Оптимальная практика работы с множественными контекстами:

<ThemeProvider value="dark">
  <UserProvider value={...}>
    <AppContent />
  </UserProvider>
</ThemeProvider>
  • Каждый компонент внутри дерева может обращаться к любому количеству контекстов.
  • Контексты обеспечивают гибкость и чистоту архитектуры, особенно в крупных приложениях, где пропсы сложно передавать через несколько уровней.

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