Композиция хуков

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


Основы хуков в Haunted

Haunted предоставляет набор хуков, таких как:

  • useState — управление локальным состоянием.
  • useEffect — выполнение побочных эффектов после рендера.
  • useReducer — сложная логика состояния.
  • useRef — сохранение мутабельных ссылок на данные.
  • useContext — доступ к контексту компонента.

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

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

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

  useEffect(() => {
    const interval = setInterval(() => setCount(c => c + 1), 1000);
    return () => clearInterval(interval);
  }, []);

  return html`<div>Count: ${count}</div>`;
}

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

Принципы композиции хуков

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

  1. Разделение логики по ответственности Один хук должен отвечать за одну задачу. Например, отдельные хуки для:

    • Управления формой (useFormState)
    • Подписки на WebSocket (useWebSocket)
    • Анимации (useAnimationFrame)
  2. Композиция через вложенные вызовы Хуки могут вызывать другие хуки внутри себя, если они остаются на верхнем уровне своей функции.

function useTimer(initial = 0) {
  const [time, setTime] = useState(initial);

  useEffect(() => {
    const interval = setInterval(() => setTime(t => t + 1), 1000);
    return () => clearInterval(interval);
  }, []);

  return time;
}

function TimerDisplay() {
  const time = useTimer();
  return html`<div>Time: ${time}</div>`;
}

customElements.define('timer-display', component(TimerDisplay));

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


Паттерны композиции

1. Хуки с параметрами Позволяют настраивать поведение внутри компонента, не дублируя логику:

function useCounter(step = 1) {
  const [count, setCount] = useState(0);
  const increment = () => setCount(c => c + step);
  const decrement = () => setCount(c => c - step);

  return { count, increment, decrement };
}

function CounterComponent() {
  const { count, increment, decrement } = useCounter(2);

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

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

2. Хуки с эффектами и очисткой ресурсов Использование useEffect внутри пользовательского хука позволяет автоматически управлять подписками и таймерами:

function useDocumentTitle(title) {
  useEffect(() => {
    const previous = document.title;
    document.title = title;
    return () => { document.title = previous; };
  }, [title]);
}

function Page() {
  useDocumentTitle('Главная страница');
  return html`<h1>Контент страницы</h1>`;
}

customElements.define('page-component', component(Page));

3. Комбинирование нескольких хуков Можно объединять несколько маленьких хуков в один “составной” хук:

function useTimerWithDocumentTitle() {
  const time = useTimer();
  useDocumentTitle(`Время: ${time}`);
  return time;
}

function TimerPage() {
  const time = useTimerWithDocumentTitle();
  return html`<p>Прошло секунд: ${time}</p>`;
}

customElements.define('timer-page', component(TimerPage));

Лучшие практики композиции

  • Каждый хук должен быть чистой функцией с минимальными побочными эффектами.
  • Не вызывать хуки условно или внутри циклов — это нарушает правила работы Haunted.
  • Разделять логические слои: одни хуки отвечают за состояние, другие — за побочные эффекты.
  • Создавать универсальные, параметризуемые хуки, которые можно повторно использовать в разных компонентах.
  • Документировать составные хуки и их зависимости, чтобы облегчить поддержку.

Влияние композиции на читаемость

Использование композиции делает компоненты короткими и понятными, выделяя отдельные аспекты поведения. Вместо одной громоздкой функции с множеством useState и useEffect появляется набор маленьких хуков, каждый из которых отвечает за конкретную задачу. Такой подход упрощает:

  • Поддержку и рефакторинг.
  • Тестирование логики вне компонента.
  • Расширяемость при добавлении новых функций.

Примеры сложной композиции

function useAuthentication() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    const token = localStorage.getItem('token');
    if (token) setUser({ name: 'Demo User' });
  }, []);

  return user;
}

function useGreeting() {
  const user = useAuthentication();
  return user ? `Привет, ${user.name}!` : 'Привет, гость!';
}

function GreetingComponent() {
  const greeting = useGreeting();
  return html`<h2>${greeting}</h2>`;
}

customElements.define('greeting-component', component(GreetingComponent));

Здесь три уровня композиции: useAuthenticationuseGreetingGreetingComponent. Такой подход облегчает повторное использование логики аутентификации и приветствия в других частях приложения.


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