Библиотеки пользовательских хуков

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


Создание функционального компонента

Функциональный компонент в Haunted определяется как обычная функция, которая возвращает шаблон разметки с помощью библиотеки lit-html. Простейший пример:

import { html, render } from 'lit-html';
import { 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 создаёт реактивное состояние.
  • Любое изменение состояния автоматически вызывает повторный рендер компонента.
  • component преобразует обычную функцию в веб-компонент.

Хуки состояния

Haunted предоставляет несколько основных хуков:

useState(initialValue)

Позволяет хранить локальное состояние компонента. Возвращает массив [value, setValue].

const [value, setValue] = useState('Hello');
setValue('World'); // обновляет компонент

useEffect(callback, dependencies)

Используется для побочных эффектов. Выполняется после рендера и повторно только при изменении зависимостей:

useEffect(() => {
  console.log('Компонент обновлён');
}, [count]);
  • Если dependencies пустой массив, эффект выполняется один раз при монтировании.
  • Если dependencies не указаны, эффект выполняется после каждого рендера.

useRef(initialValue)

Создаёт объект-ссылку, которая сохраняется между рендерами:

const inputRef = useRef();
<input ref=${inputRef} />;
inputRef.current.focus();

useReducer(reducer, initialState)

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

const [state, dispatch] = useReducer((state, action) => {
  switch(action.type) {
    case 'increment': return state + 1;
    case 'decrement': return state - 1;
    default: return state;
  }
}, 0);

Пользовательские хуки

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

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

  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);

  return { count, increment, decrement };
}

function Counter() {
  const { count, increment, decrement } = useCounter(5);

  return html`
    <div>
      <p>Счётчик: ${count}</p>
      <button @click=${decrement}>-</button>
      <button @click=${increment}>+</button>
    </div>
  `;
}

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

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

Работа с контекстом

Haunted поддерживает контекст, что позволяет передавать данные глубоко в дерево компонентов без передачи через props:

import { createContext, useContext } from 'haunted';

const ThemeContext = createContext('light');

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return html`<button class=${theme}>Нажми меня</button>`;
}

function App() {
  return html`
    <${ThemeContext.Provider} value="dark">
      <${ThemedButton}></${ThemedButton}>
    </${ThemeContext.Provider}>
  `;
}
  • createContext создаёт контекст с начальным значением.
  • useContext получает текущее значение из ближайшего провайдера.
  • Провайдер оборачивает дочерние компоненты и передаёт значение через дерево.

Рендеринг и обновления

Haunted оптимизирует рендеринг через лит-html, обновляя только изменившиеся части DOM. Основные правила:

  • Изменение состояния через useState или useReducer триггерит обновление.
  • Хуки выполняются в строгом порядке: изменение последовательности вызова нарушает логику.
  • Любой побочный эффект должен обрабатываться через useEffect, чтобы избежать неконтролируемых обновлений DOM.

Интеграция с другими библиотеками

Haunted легко интегрируется с любыми библиотеками, работающими через DOM. Например, подключение стороннего UI-компонента:

function MapComponent({ coords }) {
  useEffect(() => {
    const map = new SomeMapLibrary.Map('map', { center: coords, zoom: 10 });
    return () => map.destroy();
  }, [coords]);

  return html`<div id="map" style="width:100%;height:300px"></div>`;
}
  • Создание и уничтожение объектов следует оборачивать в useEffect для корректного управления жизненным циклом.
  • Можно комбинировать хуки Haunted с обычными событиями и слушателями DOM.

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

  • Разделять логику хуков в отдельные файлы.
  • Создавать пользовательские хуки для часто используемых функций состояния или побочных эффектов.
  • Минимизировать количество вложенных useEffect, стараясь объединять связанные действия.
  • Использовать контексты для глобальных данных (тема, авторизация, настройки), чтобы избежать проп-дриллинга.

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