Паттерны взаимодействия

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


Использование свойств (Props) и атрибутов

Компоненты Haunted получают входные данные через свойства (props). Свойства позволяют компоненту быть переиспользуемым и независимым.

import { html, component } from 'haunted';

function MyButton({ label, onClick }) {
  return html`<button @click=${onClick}>${label}</button>`;
}

customElements.define('my-button', component(MyButton));

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

  • Атрибуты HTML автоматически конвертируются в свойства через веб-компоненты.
  • Использование объектов или функций в свойствах позволяет компонентам взаимодействовать с внешним кодом без утечки состояния.
  • Изменение свойств вызывает перерисовку компонента с новыми данными.

Локальное состояние через useState

Для управления внутренним состоянием компонентов используется хук 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 позволяет реагировать на изменения состояния, свойств или выполнение побочных эффектов, аналогично React.

import { useEffect, useState, html, component } 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));

Особенности:

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

Взаимодействие компонентов через события

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

function ChildComponent({ onNotify }) {
  return html`<button @click=${() => onNotify('данные от ребёнка')}>Отправить</button>`;
}

function ParentComponent() {
  const handleNotify = (message) => console.log('Сообщение:', message);

  return html`<child-component .onNot ify=${handleNotify}></child-component>`;
}

customElements.define('parent-component', component(ParentComponent));
customElements.define('child-component', component(ChildComponent));

Особенности взаимодействия:

  • Использование функций в свойствах — более безопасный способ, чем глобальные события.
  • Можно применять dispatchEvent с CustomEvent, чтобы компонент оставался независимым и изолированным.
  • Такой паттерн обеспечивает односторонний поток данных, повышая предсказуемость поведения.

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

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

import { provide, consume } from 'haunted';

const ThemeContext = Symbol('theme');

function ThemeProvider({ theme, children }) {
  provide(ThemeContext, theme);
  return html`${children}`;
}

function ThemedButton() {
  const theme = consume(ThemeContext);
  return html`<button style="background:${theme.background}; color:${theme.color}">Кнопка</button>`;
}

Особенности:

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

Оптимизация производительности и мемоизация

Для предотвращения лишних рендеров в Haunted используется useMemo и useCallback.

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

function ExpensiveCalculation({ value }) {
  const result = useMemo(() => {
    console.log('Вычисление...');
    return value ** 2;
  }, [value]);

  return html`<p>Результат: ${result}</p>`;
}

Особенности:

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

Подписки и асинхронные потоки

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

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

function DataFetcher({ url }) {
  const [data, setData] = useState(null);

  useEffect(() => {
    let cancelled = false;
    fetch(url)
      .then(res => res.json())
      .then(json => !cancelled && setData(json));

    return () => { cancelled = true; };
  }, [url]);

  return html`<pre>${JSON.stringify(data, null, 2)}</pre>`;
}

Особенности:

  • Отмена подписок предотвращает обновление состояния после размонтирования компонента.
  • Можно использовать любые источники данных, соблюдая реактивный паттерн.
  • Комбинируя с useState и useEffect, компоненты остаются чистыми и предсказуемыми.

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