Создание собственных хуков

Haunted — это легковесная библиотека для создания реактивных веб-компонентов на основе стандартов Web Components. Одним из ключевых механизмов, обеспечивающих реактивность, являются хуки. Они позволяют управлять состоянием, жизненным циклом компонента и побочными эффектами аналогично хукам в React. Помимо встроенных хуков (useState, useEffect, useReducer), библиотека предоставляет возможность создавать собственные хуки, расширяя функциональность компонентов и повышая переиспользуемость логики.


Принципы работы пользовательских хуков

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

  1. Вызывать хуки только в теле функции компонента или другого хука. Вне компонента вызов хуков приведет к ошибкам, так как Haunted привязывает состояние к конкретному экземпляру компонента.

  2. Сохранять порядок вызова хуков. Haunted идентифицирует хуки по порядку вызова внутри компонента. Нарушение порядка приведет к некорректной работе состояния.

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


Создание простого пользовательского хука

Рассмотрим пример хука для управления счётчиком:

import { useState } from 'haunted';

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

  const increment = () => setCount(c => c + 1);
  const decrement = () => setCount(c => c - 1);
  const reset = () => setCount(initialValue);

  return { count, increment, decrement, reset };
}

Разбор структуры:

  • useState используется внутри хука для хранения состояния.
  • Внутри функции создаются функции управления состоянием (increment, decrement, reset).
  • Возвращается объект с состоянием и функциями, предоставляющий удобный интерфейс для компонента.

Использование хука в компоненте:

import { html, component } from 'haunted';

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

  return html`
    <div>
      <p>Счёт: ${count}</p>
      <button @click=${increment}>+</button>
      <button @click=${decrement}>-</button>
      <button @click=${reset}>Сброс</button>
    </div>
  `;
}

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

Пользовательский хук с эффектами

Haunted предоставляет useEffect для работы с побочными эффектами. Пользовательский хук может оборачивать useEffect для реализации сложной логики.

Пример: хук для автоматического увеличения счётчика через интервал:

import { useState, useEffect } from 'haunted';

function useAutoIncrement(initialValue = 0, interval = 1000) {
  const [count, setCount] = useState(initialValue);

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

  return { count };
}

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

  • useEffect внутри хука автоматически очищает интервал при размонтировании компонента.
  • Зависимости эффекта ([interval]) позволяют корректно обновлять таймер при изменении интервала.

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

function AutoCounter() {
  const { count } = useAutoIncrement(0, 500);

  return html`<p>Автосчётчик: ${count}</p>`;
}
customElements.define('auto-counter', component(AutoCounter));

Хуки с подпиской на внешние источники данных

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

import { useState, useEffect } from 'haunted';

function useWindowSize() {
  const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight });

  useEffect(() => {
    const handleResize = () => setSize({ width: window.innerWidth, height: window.innerHeight });
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return size;
}

Преимущества:

  • Компонент автоматически реагирует на изменения размера окна.
  • Вся логика подписки и отписки инкапсулирована в хуке, повышая чистоту кода.

Пример использования:

function WindowInfo() {
  const { width, height } = useWindowSize();

  return html`<p>Размер окна: ${width} x ${height}</p>`;
}
customElements.define('window-info', component(WindowInfo));

Параметризация хуков

Хуки могут принимать параметры для динамической настройки поведения. Например, хук для управления таймером с настраиваемым шагом:

import { useState, useEffect } from 'haunted';

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

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

  return { time };
}
  • Параметр step позволяет изменять логику работы таймера без изменения самого хука.
  • Хуки становятся гибкими и легко переиспользуемыми в различных компонентах.

Совмещение нескольких пользовательских хуков

В Haunted хуки могут использоваться совместно. Например, объединение счётчика и отслеживания размера окна:

function ResponsiveCounter() {
  const { count, increment } = useCounter();
  const { width } = useWindowSize();

  return html`
    <div>
      <p>Счёт: ${count}</p>
      <p>Ширина окна: ${width}</p>
      <button @click=${increment}>+</button>
    </div>
  `;
}
customElements.define('responsive-counter', component(ResponsiveCounter));
  • Компонент остаётся простым и понятным.
  • Логика состояния и подписок полностью вынесена в хуки.

Рекомендации при создании пользовательских хуков

  • Минимализм интерфейса: возвращать только необходимые данные и функции.
  • Изоляция побочных эффектов: все эффекты должны управляться внутри хука.
  • Использование зависимостей: правильно задавать массив зависимостей в useEffect для предотвращения лишних перерисовок.
  • Переиспользуемость: хуки должны быть независимыми от конкретного компонента.

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