Haunted — это библиотека для создания веб-компонентов на основе Web Components и функционального подхода к состоянию. Она строится на принципе composition over inheritance, что подразумевает использование функций и композиции вместо классической иерархии наследования. Такой подход облегчает повторное использование кода, тестирование и делает архитектуру компонентов более гибкой.
В Haunted компоненты создаются с помощью функций, а не классов. Это позволяет сосредоточиться на поведении и состоянии компонента, избегая громоздких цепочек наследования.
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));
Ключевой момент: состояние хранится локально через
useState, а функциональный компонент может быть легко
вложен в другой компонент.
Haunted предоставляет хуки (useState,
useEffect, useReducer и т.д.), которые
позволяют комбинировать поведение и состояние без
наследования.
function useToggle(initial = false) {
const [on, setOn] = useState(initial);
const toggle = () => setOn(!on);
return [on, toggle];
}
function ToggleButton() {
const [on, toggle] = useToggle();
return html`
<button @click=${toggle}>${on ? 'Включено' : 'Выключено'}</button>
`;
}
customElements.define('toggle-button', component(ToggleButton));
Здесь useToggle — отдельная функция с поведением,
которую можно многократно использовать в разных компонентах. Это
композиция поведения вместо наследования классов.
В 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 CounterControls() {
const { count, increment, decrement } = useCounter();
return html`
<div>
<p>Значение: ${count}</p>
<button @click=${increment}>+</button>
<button @click=${decrement}>-</button>
</div>
`;
}
customElements.define('counter-controls', component(CounterControls));
Важно: каждая функция отвечает за конкретное поведение и состояние, что делает код модульным и легко расширяемым.
Haunted поддерживает концепцию Higher-Order Components (HOC) для повторного использования логики.
function withLogging(componentFn) {
return function(props) {
console.log('Рендер компонента', componentFn.name, props);
return componentFn(props);
};
}
function Hello({ name }) {
return html`<p>Привет, ${name}</p>`;
}
customElements.define('hello-logged', component(withLogging(Hello)));
HOC позволяет добавлять функциональность к компоненту без изменения его внутренней реализации, что является прямым примером композиции.
В Haunted компоненты взаимодействуют через props, а не через наследование:
function UserCard({ user }) {
return html`
<div>
<h2>${user.name}</h2>
<p>${user.email}</p>
</div>
`;
}
function App() {
const user = { name: 'Иван', email: 'ivan@example.com' };
return html`<user-card .user=${user}></user-card>`;
}
customElements.define('app-root', component(App));
Преимущество: компоненты полностью изолированы и легко тестируются, а изменение поведения достигается через составление других компонентов, а не через наследование.
Haunted поощряет вложенные компоненты, где каждый элемент выполняет свою функцию, а сложные интерфейсы строятся из простых:
function Modal({ title, content }) {
return html`
<div class="modal">
<h1>${title}</h1>
<div>${content}</div>
</div>
`;
}
function App() {
return html`
<modal
.title=${'Приветствие'}
.content=${html`<p>Добро пожаловать!</p>`}
></modal>
`;
}
customElements.define('app-modal', component(App));
Здесь Modal — независимый компонент, который можно
использовать в разных местах приложения, комбинируя с любым
контентом.
Основная идея Haunted — создавать компоненты как чистые
функции с состоянием и поведением, комбинируя их через хуки,
props и HOC. Наследование классов здесь используется лишь для
регистрации элементов (customElements.define), но логика
полностью строится через композицию. Такой подход облегчает
масштабирование, переиспользование и поддержку сложных приложений.