Haunted — это легковесная библиотека для создания веб-компонентов с использованием реактивного подхода, вдохновлённого React Hooks. Одним из ключевых аспектов эффективной разработки в Haunted является повторное использование логики, что позволяет строить масштабируемые и поддерживаемые приложения без дублирования кода.
В Haunted компоненты создаются как обычные функции JavaScript,
возвращающие шаблон html из библиотеки
lit-html:
import { html, render } from 'lit-html';
import { component, useState, useEffect } from 'haunted';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => setCount(c => c + 1), 1000);
return () => clearInterval(interval);
}, []);
return html`
<div>
<p>Счётчик: ${count}</p>
<button @click=${() => setCount(count + 1)}>+</button>
<button @click=${() => setCount(count - 1)}>-</button>
</div>
`;
}
customElements.define('my-counter', component(Counter));
Ключевые моменты:
useState создаёт реактивное состояние, обновляющее DOM
при изменении значения.useEffect управляет побочными эффектами, аналогично
React.Haunted позволяет создавать собственные хуки, которые инкапсулируют повторяемую логику:
import { useState, useEffect } from 'haunted';
function useTimer(initial = 0) {
const [time, setTime] = useState(initial);
useEffect(() => {
const id = setInterval(() => setTime(t => t + 1), 1000);
return () => clearInterval(id);
}, []);
return time;
}
Использование собственного хука в компоненте:
function TimerDisplay() {
const time = useTimer(10);
return html`<p>Время: ${time} сек.</p>`;
}
customElements.define('timer-display', component(TimerDisplay));
Преимущества:
Хуки в Haunted могут принимать аргументы, что делает их гибкими и адаптируемыми:
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(json => setData(json))
.finally(() => setLoading(false));
}, [url]);
return { data, loading };
}
function UserList() {
const { data, loading } = useFetch('https://jsonplaceholder.typicode.com/users');
if (loading) return html`<p>Загрузка...</p>`;
return html`
<ul>
${data.map(user => html`<li>${user.name}</li>`)}
</ul>
`;
}
customElements.define('user-list', component(UserList));
Ключевые особенности:
url в примере) автоматически
инициирует новый эффект.Haunted поддерживает составные хуки, что упрощает повторное использование сложной логики:
function useCounterWithTimer(initialCount = 0) {
const [count, setCount] = useState(initialCount);
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const id = setInterval(() => setSeconds(s => s + 1), 1000);
return () => clearInterval(id);
}, []);
return { count, setCount, seconds };
}
function CounterWithTimer() {
const { count, setCount, seconds } = useCounterWithTimer();
return html`
<div>
<p>Счётчик: ${count}</p>
<p>Время: ${seconds}</p>
<button @click=${() => setCount(count + 1)}>+</button>
<button @click=${() => setCount(count - 1)}>-</button>
</div>
`;
}
customElements.define('counter-with-timer', component(CounterWithTimer));
Выводы:
Haunted поддерживает контекст через хуки createContext и
useContext, что позволяет поделиться логикой между
компонентами, избегая проп-дриллинга:
import { createContext, useContext } from 'haunted';
const ThemeContext = createContext('light');
function ThemeProvider({ theme, children }) {
return html`<${ThemeContext.Provider} value=${theme}>${children}</${ThemeContext.Provider}>`;
}
function ThemedText() {
const theme = useContext(ThemeContext);
return html`<p>Текущая тема: ${theme}</p>`;
}
Преимущества контекста:
Haunted предоставляет гибкий и минималистичный способ инкапсуляции логики, позволяя создавать масштабируемые веб-компоненты с реактивным поведением. Повторное использование хуков и контекста делает код более чистым, читаемым и тестируемым, обеспечивая долгосрочную поддержку приложений.