Haunted — это небольшая библиотека для создания веб-компонентов с
использованием функционального подхода, основанного на хуках, похожих на
React. Помимо декларативного стиля с хуками useState,
useEffect и другими, Haunted предоставляет
императивный API, который позволяет управлять жизненным
циклом компонентов и их состоянием более детально, напрямую
взаимодействуя с DOM и объектами компонентов.
component()Функция component() — основной инструмент для
императивного создания Haunted-компонента. Она принимает функцию
рендера, возвращающую шаблон (обычно с использованием
html из lit-html), и опциональный
массив хуков для внедрения функциональности:
import { component, html } from 'haunted';
function MyCounter({ count = 0 }) {
return html`<p>Счётчик: ${count}</p>`;
}
customElements.define('my-counter', component(MyCounter));
component(renderFunction) возвращает класс
веб-компонента, который можно зарегистрировать через
customElements.define.props —
набором свойств компонента.useStateХотя это часть хуков, использование useState через
императивный API позволяет управлять состоянием без сложной
инфраструктуры:
import { component, useState, html } from 'haunted';
function Counter() {
const [count, setCount] = useState(0);
return html`
<div>
<p>Счёт: ${count}</p>
<button @click=${() => setCount(count + 1)}>Увеличить</button>
</div>
`;
}
customElements.define('counter-element', component(Counter));
Особенности императивного управления:
setCount обновляет состояние и вызывает повторный
рендер компонента.Imперативный API позволяет работать с методами жизненного цикла веб-компонентов напрямую:
connectedCallback() — вызывается при добавлении
элемента в DOM.disconnectedCallback() — при удалении из DOM.adoptedCallback() — при перемещении элемента между
документами.attributeChangedCallback(attrName, oldVal, newVal) —
при изменении атрибутов.Haunted позволяет внедрять хуки жизненного цикла:
import { component, useEffect, html } from 'haunted';
function Logger() {
useEffect(() => {
console.log('Компонент добавлен в DOM');
return () => console.log('Компонент удалён из DOM');
}, []);
return html`<p>Проверка жизненного цикла</p>`;
}
customElements.define('logger-element', component(Logger));
useEffect(callback, deps) используется для наблюдения
за изменениями зависимостей.Компоненты Haunted могут получать атрибуты как свойства и реагировать на их изменения. Прямое обновление пропсов через API выглядит так:
const counter = document.createElement('counter-element');
counter.count = 10;
document.body.appendChild(counter);
// Позднее обновление
counter.count = 20;
attributeChangedCallback совместно с
observedAttributes.Императивный подход в Haunted не ограничивается шаблонами:
import { component, html } from 'haunted';
function Wrapper() {
return html`
<div id="container"></div>
`;
}
customElements.define('wrapper-element', component(Wrapper));
const wrapper = document.querySelector('wrapper-element');
const container = wrapper.shadowRoot.getElementById('container');
container.innerHTML = '<p>Императивный контент</p>';
component().shadowRoot.Imперативный API поддерживает наследование и миксины, что полезно для создания переиспользуемых компонентов:
class BaseElement extends HTMLElement {
log(msg) {
console.log(`[Base] ${msg}`);
}
}
function enhancedComponent(fn) {
return class extends BaseElement {
connectedCallback() {
super.connectedCallback?.();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = fn();
}
};
}
customElements.define('enhanced-element', enhancedComponent(() => '<p>Расширенный компонент</p>'));
Haunted предоставляет функцию render() для
императивного обновления DOM без хука состояния:
import { render, html } from 'haunted';
const root = document.getElementById('root');
let counter = 0;
function update() {
render(html`<p>Счётчик: ${counter}</p>`, root);
}
setInterval(() => {
counter++;
update();
}, 1000);
render(templateResult, container) повторно рендерит
шаблон.component().Haunted позволяет работать с событиями в стиле нативного DOM, либо через шаблон:
import { component, html } from 'haunted';
function Clicker() {
const handleClick = () => alert('Клик!');
return html`<button @click=${handleClick}>Нажми меня</button>`;
}
customElements.define('clicker-element', component(Clicker));
el.oncl ick = ...) или через шаблон
(@click).Императивный API в Haunted сочетает гибкость веб-компонентов и удобство хуков, предоставляя полный контроль над состоянием, рендером и жизненным циклом компонентов, позволяя интегрировать функциональность как декларативно, так и напрямую через DOM.