Haunted — это современная библиотека для создания веб-компонентов на основе реактивных хуков в JavaScript. Она позволяет работать с состоянием компонентов, реактивными эффектами и контекстом без необходимости использовать крупные фреймворки вроде React или Vue. В контексте маршрутизации (роутинга) Haunted предоставляет гибкую интеграцию с внешними библиотеками и собственные подходы к управлению отображением страниц и переходами.
В Haunted состояние компонента обычно создается с помощью хука
useState. Для роутера это означает хранение текущего
маршрута в реактивной переменной:
import { html, useState } from 'haunted';
function Router() {
const [route, setRoute] = useState(window.location.pathname);
window.addEventListener('popstate', () => setRoute(window.location.pathname));
return html`
${route === '/' ? html`<home-page></home-page>` : ''}
${route === '/about' ? html`<about-page></about-page>` : ''}
`;
}
customElements.define('app-router', Router);
Ключевой момент: использование popstate
позволяет корректно отслеживать изменения URL, в том числе при навигации
назад и вперед в браузере.
Для переходов между страницами без полной перезагрузки используется
history.pushState:
function navigate(path) {
window.history.pushState({}, '', path);
window.dispatchEvent(new Event('popstate'));
}
В связке с реактивным состоянием Haunted это обеспечивает мгновенную реакцию интерфейса на изменение маршрута.
Поддержка динамических сегментов URL реализуется через парсинг строки маршрута. Например, для страницы с профилем пользователя:
function Router() {
const [route, setRoute] = useState(window.location.pathname);
window.addEventListener('popstate', () => setRoute(window.location.pathname));
const userMatch = route.match(/^\/user\/(\d+)$/);
return html`
${route === '/' ? html`<home-page></home-page>` : ''}
${route === '/about' ? html`<about-page></about-page>` : ''}
${userMatch ? html`<user-page userId="${userMatch[1]}"></user-page>` : ''}
`;
}
Важно: регулярные выражения позволяют реализовать маршруты с параметрами, сохраняя простоту кода и реактивность интерфейса.
Haunted предоставляет useContext и
createContext, что позволяет хранить информацию о маршрутах
и навигации на уровне всего приложения:
import { createContext, useContext } from 'haunted';
const RouteContext = createContext();
function RouterProvider({ children }) {
const [route, setRoute] = useState(window.location.pathname);
window.addEventListener('popstate', () => setRoute(window.location.pathname));
return html`<${RouteContext.Provider} .value=${{ route, setRoute }}>
${children}
</${RouteContext.Provider}>`;
}
function Link({ path, label }) {
const { setRoute } = useContext(RouteContext);
return html`<a href="${path}" @click=${e => { e.preventDefault(); setRoute(path); window.history.pushState({}, '', path); }}>
${label}
</a>`;
}
Выделение ключевого аспекта: использование контекста позволяет любому компоненту приложения подписываться на изменения маршрута без необходимости пробрасывать пропсы через все уровни.
Для крупных приложений важно загружать страницы по мере необходимости. Haunted совместим с динамическим импортом модулей:
async function loadPage(route) {
switch(route) {
case '/about':
await import('./about-page.js');
return html`<about-page></about-page>`;
case '/':
await import('./home-page.js');
return html`<home-page></home-page>`;
default:
return html`<not-found-page></not-found-page>`;
}
}
function Router() {
const [route, setRoute] = useState(window.location.pathname);
const [page, setPage] = useState(null);
window.addEventListener('popstate', () => setRoute(window.location.pathname));
useEffect(() => {
loadPage(route).then(setPage);
}, [route]);
return page;
}
Ключевое преимущество: реактивность Haunted позволяет динамически обновлять интерфейс при асинхронной подгрузке страниц без ручного вмешательства в DOM.
Для реализации вложенных маршрутов используют дочерние роутеры. Каждый роутер отвечает за собственный сегмент URL:
function UserPage({ userId }) {
const [subRoute, setSubRoute] = useState(window.location.hash || '#overview');
window.addEventListener('hashchange', () => setSubRoute(window.location.hash));
return html`
<nav>
<a href="#overview">Overview</a>
<a href="#settings">Settings</a>
</nav>
${subRoute === '#overview' ? html`<user-overview .id=${userId}></user-overview>` : ''}
${subRoute === '#settings' ? html`<user-settings .id=${userId}></user-settings>` : ''}
`;
}
Замечание: использование hashchange
позволяет создать простой, но эффективный механизм вложенной навигации,
не вмешиваясь в глобальный роутер приложения.
Для плавного перехода между страницами Haunted поддерживает реактивные эффекты, которые можно сочетать с CSS-анимациями:
function Router() {
const [route, setRoute] = useState(window.location.pathname);
const [page, setPage] = useState(null);
const [visible, setVisible] = useState(false);
window.addEventListener('popstate', () => setRoute(window.location.pathname));
useEffect(() => {
setVisible(false);
loadPage(route).then(p => { setPage(p); setVisible(true); });
}, [route]);
return html`<div class=${visible ? 'fade-in' : 'fade-out'}>${page}</div>`;
}
Ключевой момент: сочетание реактивного состояния и CSS позволяет создавать плавные переходы между страницами без сторонних библиотек для анимации.
Haunted в контексте роутинга демонстрирует следующие возможности:
useState.pushState и
popstate.Такой подход позволяет строить сложные SPA на чистых веб-компонентах с минимальными зависимостями, сохраняя реактивность и модульность интерфейса.