Распространенные паттерны

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

Ключевые хуки в Haunted:

  • useState(initialValue) — создаёт локальное состояние компонента.
  • useEffect(callback, dependencies) — выполняет побочные эффекты при изменении зависимостей.
  • useRef(initialValue) — создаёт ссылку на DOM-элемент или любое значение, сохраняемое между рендерами.
  • useContext(context) — позволяет получать значения из контекста.
  • useReducer(reducer, initialState) — управляет сложным состоянием через редьюсер.

Haunted использует tagged template literals для шаблонов HTML через функцию html, что обеспечивает декларативный рендеринг.


Определение компонентов

Компоненты в Haunted определяются функцией и регистрируются через customElements.define. Основная структура компонента выглядит следующим образом:

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));

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

  • component(Counter) превращает обычную функцию в полноценный веб-компонент.
  • Шаблон возвращается через `html`` — это реактивный рендер.
  • Изменение состояния через setCount автоматически перерисовывает компонент.

Управление состоянием

useState

useState — базовый инструмент для реактивного состояния. Он возвращает массив [state, setState], где state — текущее значение, а setState — функция для его обновления.

const [text, setText] = useState('Привет');

function handleChange(event) {
  setText(event.target.value);
}

При каждом вызове setText компонент перерисовывается с новым значением. В Haunted useState полностью изолирован внутри компонента — изменения не влияют на другие экземпляры компонента.

useReducer

Для сложного состояния, например с несколькими свойствами или зависимостями, используется useReducer:

const initialState = { count: 0, step: 1 };

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

const [state, dispatch] = useReducer(reducer, initialState);

Этот подход особенно полезен, когда состояние связано и требует последовательного обновления.


Эффекты и жизненный цикл

useEffect управляет побочными эффектами, аналогично методам жизненного цикла componentDidMount и componentDidUpdate.

useEffect(() => {
  console.log('Компонент смонтирован или обновился');

  return () => console.log('Компонент размонтирован');
}, [count]);
  • Колбэк вызывается после рендера.
  • Если указаны зависимости, эффект срабатывает только при их изменении.
  • Функция, возвращаемая из эффекта, выполняется при размонтировании или перед повторным вызовом эффекта.

Рефы и доступ к DOM

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

const inputRef = useRef();

function focusInput() {
  inputRef.current.focus();
}

return html`
  <input ${ref(inputRef)} type="text">
  <button @click=${focusInput}>Фокус</button>
`;
  • ref(inputRef) привязывает DOM-элемент к объекту inputRef.
  • Это особенно полезно для управления фокусом, интеграции с сторонними библиотеками или анимациями.

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

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

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`<theme-context value="dark">
    <themed-button></themed-button>
  </theme-context>`;
}

Контекст упрощает управление глобальным состоянием и темами в приложении.


Обработка событий

Haunted использует синтаксис @event для привязки обработчиков:

<button @click=${handleClick}>Нажми</button>
  • Поддерживаются все стандартные события DOM.
  • Аргументы передаются через замыкания.
  • Изменение состояния в обработчике автоматически инициирует перерисовку компонента.

Расширенные паттерны

Композиция компонентов

Компоненты в Haunted легко комбинируются:

function Dialog({ title, content }) {
  return html`
    <div class="dialog">
      <h2>${title}</h2>
      <p>${content}</p>
    </div>
  `;
}

function App() {
  return html`
    ${Dialog({ title: 'Привет', content: 'Содержимое диалога' })}
  `;
}
  • Функции-компоненты могут вызываться внутри других компонентов.
  • Комбинация хуков внутри дочерних компонентов полностью независима от родителя.

Хуки для повторного использования логики

Можно создавать собственные хуки для повторного использования состояния или эффектов:

function useCounter(initial = 0) {
  const [count, setCount] = useState(initial);
  const increment = () => setCount(count + 1);
  return [count, increment];
}

function Counter() {
  const [count, increment] = useCounter(0);
  return html`<button @click=${increment}>${count}</button>`;
}

Это повышает читаемость кода и разделяет логику и шаблон.


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

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

  • Шаблон возвращается функцией `html``.
  • Все динамические значения внутри ${} автоматически отслеживаются.
  • Изменение состояния вызывает диффинг шаблона и минимальные изменения в DOM.

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

Haunted легко интегрируется с сторонними библиотеками:

  • Для стилей можно использовать lit-css или обычные CSS-модули.
  • Для маршрутизации можно подключить любую библиотеку SPA.
  • Можно использовать сторонние UI-компоненты, оборачивая их в Haunted-компоненты через component().

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