Потребление контекста

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

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

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

import { createContext } from 'haunted';

const ThemeContext = createContext({
  theme: 'light',
  toggleTheme: () => {}
});

Важно: объект, переданный в createContext, используется как значение по умолчанию. Если компонент-потребитель находится вне Provider, он будет получать именно это значение.

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

Provider применяется для оборачивания части дерева компонентов, которым необходимо предоставить доступ к контексту. Provider принимает проп value, который определяет текущее значение контекста для всех вложенных компонентов.

import { component, useState } from 'haunted';

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  const toggleTheme = () => setTheme(theme === 'light' ? 'dark' : 'light');

  return html`
    <${ThemeContext.Provider} .value=${{ theme, toggleTheme }}>
      ${children}
    <//>
  `;
}

customElements.define('theme-provider', component(ThemeProvider));

Ключевой момент: обновление значения value автоматически триггерит повторный рендер всех потребителей этого контекста, что обеспечивает реактивность данных.

Потребление контекста с useContext

Для доступа к значению контекста используется хук useContext. Он принимает объект контекста и возвращает текущее значение, предоставленное ближайшим Provider.

import { useContext } from 'haunted';

function ThemeButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return html`
    <button @click=${toggleTheme}>
      Текущая тема: ${theme}
    </button>
  `;
}

customElements.define('theme-button', component(ThemeButton));

Особенности работы useContext:

  • Подписка на изменения контекста происходит автоматически. Любое изменение value в Provider вызовет повторный рендер потребителей.
  • Если компонент находится вне области действия Provider, возвращается значение по умолчанию, указанное при создании контекста.
  • useContext можно вызывать внутри любого компонента, созданного через component, и внутри других хуков Haunted.

Контексты и вложенные компоненты

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

function NestedComponent() {
  const { theme } = useContext(ThemeContext);

  return html`
    <p>Тема внутри вложенного компонента: ${theme}</p>
  `;
}

function App() {
  return html`
    <${ThemeContext.Provider} .value=${{ theme: 'dark' }}>
      <nested-component></nested-component>
    <//>
  `;
}

В этом примере NestedComponent получает значение theme из ближайшего провайдера, даже если дерево компонентов гораздо глубже.

Комбинирование нескольких контекстов

Для управления несколькими типами состояния можно использовать несколько контекстов одновременно. Каждый контекст создаётся и предоставляется отдельно, а потребители используют соответствующий useContext.

const AuthContext = createContext({ user: null });

function Profile() {
  const { theme } = useContext(ThemeContext);
  const { user } = useContext(AuthContext);

  return html`
    <div>
      <p>Пользователь: ${user?.name || 'Гость'}</p>
      <p>Текущая тема: ${theme}</p>
    </div>
  `;
}

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

Контекст и производительность

Использование контекста в Haunted накладывает следующие особенности:

  • Повторный рендер потребителей происходит только при изменении значения value. Если объект не изменяется по ссылке, рендер не вызывается.
  • Для сложных объектов рекомендуется мемоизация значения value, например с использованием useState или useMemo, чтобы избежать ненужных обновлений.
const value = useMemo(() => ({ theme, toggleTheme }), [theme]);

Особенности интеграции с веб-компонентами

  • Контекст полностью совместим с нативными веб-компонентами. Любой компонент, обернутый в Provider, может быть обычным кастомным элементом или Haunted-компонентом.
  • Передача контекста через slots или динамические DOM-элементы работает корректно, так как Provider использует Shadow DOM-aware механизм подписки.

Практические советы

  • Использовать контекст следует для глобального состояния или состояния, которым пользуются несколько независимых компонентов.
  • Для локального состояния компонентов лучше применять useState и пробрасывать данные через пропсы.
  • Контекст хорошо подходит для темизации, управления авторизацией, настроек локали и других “сквозных” данных.

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