Инициализация через функцию

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


Основы функциональной инициализации

В Haunted компонент определяется как функция, возвращающая шаблон HTML с использованием html из haunted. Функция может принимать свойства компонента через аргумент и использовать hooks для управления состоянием и побочными эффектами.

import { html, component } from 'haunted';

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

  return html`
    <div>
      <p>Счётчик: ${count}</p>
      <button @click=${() => setCount(count + 1)}>Увеличить</button>
    </div>
  `;
}

customElements.define('my-counter', component(MyCounter));

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

  • component(MyCounter) превращает обычную функцию в полноценный веб-компонент.
  • useState создаёт локальное состояние, доступное внутри функции.
  • Аргумент функции { initial } автоматически связывается с атрибутами элемента.

Свойства и атрибуты

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

<my-counter initial="10"></my-counter>

Внутри функции значение initial будет числом 10, если определить тип свойства при регистрации:

component(MyCounter, { observedAttributes: ['initial'] });

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

Функциональные компоненты Haunted поддерживают следующие основные хуки:

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

Пример с useEffect:

function Timer({ interval = 1000 }) {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const id = setInterval(() => setSeconds(s => s + 1), interval);
    return () => clearInterval(id);
  }, [interval]);

  return html`<p>Прошло секунд: ${seconds}</p>`;
}

customElements.define('my-timer', component(Timer));
  • Эффект создаёт таймер и автоматически очищается при размонтировании или изменении interval.
  • Зависимости в массиве [interval] определяют, когда эффект будет перезапущен.

Вложенные компоненты и композиция

Функциональные компоненты удобно комбинировать. Любой компонент можно использовать внутри другого, передавая свойства и слоты:

function Card({ title, children }) {
  return html`
    <div class="card">
      <h3>${title}</h3>
      <div>${children}</div>
    </div>
  `;
}

customElements.define('my-card', component(Card));

function App() {
  return html`
    <my-card title="Пример">
      <p>Содержимое карточки</p>
    </my-card>
  `;
}

customElements.define('my-app', component(App));
  • children автоматически получает содержимое слота.
  • Вложенные компоненты полностью управляются реактивностью Haunted.

Передача событий

События можно передавать через кастомные события, которые удобно использовать в функциональных компонентах:

function Button({ label }) {
  const handleClick = () => dispatchEvent(new CustomEvent('action', { bubbles: true }));
  return html`<button @click=${handleClick}>${label}</button>`;
}

customElements.define('my-button', component(Button));

function Toolbar() {
  const handleAction = (e) => console.log('Кнопка нажата', e);
  return html`<my-button label="Нажми" @action=${handleAction}></my-button>`;
}

customElements.define('my-toolbar', component(Toolbar));
  • События можно слушать с помощью @eventName=${handler}.
  • Использование bubbles: true позволяет событиям всплывать до родительских компонентов.

Реактивные свойства внутри функций

Haunted обеспечивает реактивность без ручного обновления DOM. Любое изменение состояния через useState или useReducer автоматически приводит к повторному рендеру функции.

function Toggle() {
  const [on, setOn] = useState(false);

  return html`
    <button @click=${() => setOn(!on)}>
      ${on ? 'Включено' : 'Выключено'}
    </button>
  `;
}

customElements.define('my-toggle', component(Toggle));
  • Переключение состояния инициирует перерендер компонента.
  • Нет необходимости напрямую работать с innerHTML или shadowRoot.

Поддержка Shadow DOM

При регистрации компонента через component(func) можно управлять использованием Shadow DOM:

customElements.define('my-shadow', component(MyComponent, { useShadowDOM: true }));
  • useShadowDOM: true создаёт изолированную область стилей.
  • useShadowDOM: false рендерит компонент в светлом DOM, что удобно для интеграции с внешними стилями.

Функциональная инициализация через функции в Haunted позволяет создавать реактивные, компактные и легко тестируемые веб-компоненты, которые полностью управляются состоянием, эффектами и событиями, минимизируя работу с низкоуровневым DOM и атрибутами.