Функция component

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


Создание компонента

Функция component принимает на вход функцию рендеринга, которая возвращает разметку в формате html из библиотеки lit-html. Сигнатура выглядит следующим образом:

import { component, html } from 'haunted';

function MyComponent({ name }) {
  return html`<p>Hello, ${name}!</p>`;
}

customElements.define('my-component', component(MyComponent));
  • MyComponent — обычная JavaScript-функция, принимающая объект с пропсами.
  • component(MyComponent) — возвращает класс кастомного элемента, который можно зарегистрировать через customElements.define.
  • Разметка возвращается с помощью html из lit-html, что обеспечивает эффективное обновление DOM.

Передача пропсов

Любые атрибуты, заданные в HTML, автоматически преобразуются в пропсы. Haunted использует реактивную привязку пропсов, что позволяет компоненту автоматически реагировать на их изменения.

<my-component name="Alice"></my-component>

Внутри функции:

function MyComponent({ name }) {
  return html`<p>Hello, ${name}!</p>`;
}

При изменении атрибута name на <my-component name="Bob"></my-component> компонент будет автоматически обновлён.


Реактивное состояние с useState

Haunted интегрирует хуки, аналогичные React. useState позволяет создавать локальное состояние компонента:

import { useState } from 'haunted';

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

  return html`
    <p>Count: ${count}</p>
    <button @click=${() => setCount(count + 1)}>Increment</button>
  `;
}

customElements.define('my-counter', component(Counter));
  • useState возвращает массив [значение, функция для обновления].
  • Любое обновление через setCount автоматически инициирует повторный рендер компонента.
  • Состояние изолировано внутри конкретного экземпляра элемента.

Хуки жизненного цикла

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

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

    import { 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>`;
    }
  • useRef — создаёт ссылку на DOM-элемент:

    import { useRef } from 'haunted';
    
    function InputFocus() {
      const inputRef = useRef();
    
      return html`
        <input ${ref(inputRef)} />
        <button @click=${() => inputRef.current.focus()}>Focus</button>
      `;
    }
  • useContext и provide — для управления глобальными данными в дереве компонентов.


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

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

<button @click=${handleClick}>Click me</button>
  • Функция handleClick может изменять состояние через хуки useState.
  • Все события автоматически привязаны к конкретному экземпляру компонента, что исключает утечки контекста.

Обновление компонента

Компоненты Haunted автоматически перерендериваются при изменении состояния или пропсов. Нет необходимости вручную обновлять DOM или использовать setState как в обычном Web Components. Ключевые моменты:

  1. Любое изменение состояния через хуки инициирует рендер.
  2. Обновление пропсов триггерит перерендер.
  3. DOM-изменения минимизированы благодаря lit-html, что повышает производительность.

Пример сложного компонента

function TodoList({ initialTodos = [] }) {
  const [todos, setTodos] = useState(initialTodos);
  const [newTodo, setNewTodo] = useState('');

  const addTodo = () => {
    if (newTodo.trim()) {
      setTodos([...todos, newTodo.trim()]);
      setNewTodo('');
    }
  };

  return html`
    <ul>
      ${todos.map(todo => html`<li>${todo}</li>`)}
    </ul>
    <input
      type="text"
      .value=${newTodo}
      @input=${e => setNewTodo(e.target.value)}
    />
    <button @click=${addTodo}>Add</button>
  `;
}

customElements.define('todo-list', component(TodoList));
  • Компонент использует несколько состояний.
  • Обновление списка и поля ввода полностью реактивное.
  • Все изменения отражаются в DOM без ручного вмешательства.

Настройка параметров component

Функция component поддерживает второй аргумент — объект настроек:

component(MyComponent, { useShadowDOM: true, observedAttributes: ['name'] });
  • useShadowDOM — включает Shadow DOM для изоляции стилей.
  • observedAttributes — позволяет указать, какие атрибуты должны отслеживаться для перерендеринга.
  • Можно настроить обработку событий и ререндеринг, что позволяет интегрировать Haunted в сложные приложения с минимальными усилиями.

Функция component в Haunted является сердцем реактивной архитектуры. Она объединяет декларативный рендеринг, хуки состояния и жизненного цикла, а также эффективное обновление DOM, создавая мощный инструмент для построения современных веб-компонентов.