Provider Pattern

Provider Pattern в Haunted упрощает передачу данных через иерархию веб-компонентов без промежуточных свойств и событий. Механизм основан на использовании контекста (context) и декораторов provide и consume. Такой подход усиливает композиционность компонентов и снижает связанность.


Основные элементы

Контекст Контекст описывает канал передачи значения. Он создаётся функцией createContext(). Созданный объект контекста используется как ключ для связывания поставщика (provider) и потребителей (consumers).

Provider Поставщик публикует значение в контекст. Это может быть состояние компонента, конфигурация приложения, сервис или любая другая модель.

Consumer Потребитель подписывается на изменения значения контекста и реагирует на них как на локальное состояние.


Определение контекста

Контекст объявляется один раз и используется в любом количестве компонентов.

import { createContext } from 'haunted';

export const themeContext = createContext();

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


Реализация Provider

Провайдер часто реализуется как компонент, обладающий собственным состоянием. Публикация значения выполняется декоратором provide.

import { component, useState, provide } from 'haunted';
import { html } from 'lit-html';
import { themeContext } from './theme-context.js';

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

  provide(themeContext, theme);

  return html`
    <slot></slot>
  `;
}

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

Ключевой момент: провайдер передаёт контекст вниз по дереву DOM до ближайших потребителей.


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

Потребитель получает значение контекста через декоратор consume. Значение обновляется при каждом изменении контекста.

import { component, consume } from 'haunted';
import { html } from 'lit-html';
import { themeContext } from './theme-context.js';

function ThemeSwitcher() {
  const theme = consume(themeContext);

  return html`
    <div>Текущая тема: ${theme}</div>
  `;
}

customElements.define('theme-switcher', component(ThemeSwitcher));

Передача динамических состояний

Provider Pattern важен при работе со сложными моделями, реактивными сторы или API-клиентами. Пример: публикация объекта вместо строки.

const userContext = createContext();

function UserProvider() {
  const [user, setUser] = useState({ name: 'Alice', role: 'admin' });

  provide(userContext, user);

  return html`<slot></slot>`;
}

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


Иерархия и переопределение значений

Контексты работают по принципу поиска ближайшего провайдера вверх по дереву. Это позволяет переопределять значения на любом уровне: общий провайдер задаёт настройки приложения, а вложенный компонент локально меняет их для дочерних элементов.

<theme-provider theme="light">
  <theme-provider theme="dark">
    <theme-switcher></theme-switcher>
  </theme-provider>
</theme-provider>

В примере потребитель получит dark, так как ближайший провайдер перекрывает вышестоящий.


Отсутствие обратных ссылок

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


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

Контексты корректно работают вместе с useState, useReducer, useMemo и другими хуками Haunted. Обновление стейта провайдера вызывает реакцию потребителей без дополнительных действий.


Использование с внешними библиотеками

Provider Pattern хорошо ложится на архитектуру, вдохновлённую React. Возможно внедрение глобальных сторах (например, Redux, Zustand, Jotai), сервисов или адаптеров API. Контекст скрывает реализацию стора и раскрывает только интерфейс данных.


Дополнительные детали реализации

Распространение значения Контекст не использует DOM-события, а полагается на внутренний механизм подписок. Это означает предсказуемые обновления без ручного подписывания.

Неконтролируемые области Контексты ограничиваются субдеревом DOM. При переносе элементов с помощью порталов или теневых DOM-границ необходимо учитывать, что провайдер может не достигать таких компонентов.


Архитектурные преимущества

Низкая связанность компонентов Потребители зависят только от контекста, а не от конкретного провайдера.

Повышенная модульность Легкое замещение провайдера позволяет создавать тестовые окружения, переопределять настройки и адаптировать приложение под различные сценарии.

Минимизация проп-дриллинга Поскольку данные не передаются по цепочке свойств, уменьшается объем кода и вероятность ошибок.


Тестирование компонентов

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

<theme-provider .theme=${'test-theme'}>
  <theme-switcher></theme-switcher>
</theme-provider>

Такой подход упрощает тестирование и демонстрирует строгие зависимости.


Практические сценарии

Темизация интерфейса Публикация информации о теме, цветовых схемах и шрифтах.

Глобальные модели Пользовательские сессии, конфигурация приложения, данные локализации.

Сервисы и API-клиенты Инициализация REST-клиентов, WebSocket-подписок, кэширования.

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


Ключевые свойства подхода

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

Использование Provider Pattern в Haunted даёт веб-компонентам средства для построения сложных интерфейсов с изолированными уровнями состояния и конфигурации, сохраняя простоту и читаемость кода.