Hooks Pattern

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

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


useState

useState — базовый Hook для управления локальным состоянием компонента.

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>
      <button @click=${() => setCount(count - 1)}>Уменьшить</button>
    </div>
  `;
}

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

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

  • Возвращает массив из текущего значения и функции для его обновления.
  • Любое обновление состояния автоматически вызывает повторный рендер компонента.
  • Можно использовать несколько состояний в одном компоненте.

useEffect

useEffect позволяет выполнять побочные эффекты после рендера компонента.

import { html, component, useState, useEffect } 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('timer-element', component(Timer));

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

  • Первый аргумент — функция эффекта.
  • Функция может возвращать очистку (cleanup) для предотвращения утечек памяти.
  • Второй аргумент — массив зависимостей; эффект выполняется только при изменении этих зависимостей.
  • Без массива зависимостей эффект выполняется после каждого рендера.

useRef

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

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

function FocusInput() {
  const inputRef = useRef();

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return html`<input type="text" ref=${inputRef} />`;
}

customElements.define('focus-input', component(FocusInput));

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

  • Используется для доступа к DOM-элементам.
  • Можно хранить значения, которые не должны вызывать повторный рендер.
  • В отличие от состояния, изменение ref.current не вызывает обновление компонента.

useReducer

useReducer предназначен для управления сложным состоянием с помощью редьюсеров, аналогично Redux.

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

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

function CounterReducer() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return html`
    <p>Счётчик: ${state.count}</p>
    <button @click=${() => dispatch({ type: 'increment' })}>+</button>
    <button @click=${() => dispatch({ type: 'decrement' })}>-</button>
  `;
}

customElements.define('counter-reducer', component(CounterReducer));

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

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

useContext и кастомные Hooks

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

import { html, component, useContext } from 'haunted';
import { createContext } from 'haunted/context';

const ThemeContext = createContext('light');

function ThemedButton() {
  const theme = useContext(ThemeContext);

  return html`<button class=${theme}>Кнопка с темой ${theme}</button>`;
}

Кастомные Hooks создаются как обычные функции, использующие встроенные Hooks:

import { useState, useEffect } from 'haunted';

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handler = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handler);
    return () => window.removeEventListener('resize', handler);
  }, []);

  return width;
}

Правила использования Hooks в Haunted

  1. Вызывать только внутри компонента — нельзя использовать Hooks в обычных функциях.
  2. Вызывать на верхнем уровне — нельзя помещать Hooks внутрь условных блоков или циклов.
  3. Соблюдать порядок — порядок вызовов Hooks должен быть одинаковым при каждом рендере.
  4. Минимизировать побочные эффекты — эффекты должны быть чистыми и правильно очищаться.

Преимущества использования Hooks

  • Чёткое разделение логики и разметки.
  • Возможность повторного использования состояния и логики.
  • Упрощение тестирования компонентов.
  • Более декларативный и понятный синтаксис по сравнению с традиционными классами веб-компонентов.

Hooks в Haunted делают компоненты лёгкими, реактивными и удобными для масштабирования больших приложений, сохраняя при этом нативную совместимость с веб-стандартами.