Haunted — это легковесная библиотека для создания реактивных веб-компонентов на основе стандартов Web Components и Hooks в стиле React. Одной из ключевых задач при построении одностраничных приложений (SPA) является организация клиентской маршрутизации — управления изменением состояния интерфейса в зависимости от URL без полной перезагрузки страницы.
Маршрутизация в Haunted не встроена напрямую, но библиотека отлично
сочетается с современными подходами к маршрутизации через кастомные хуки
и слушатели событий popstate или использование сторонних
маршрутизаторов, таких как Vaadin
Router.
useRoute
для отслеживания путиДля управления состоянием маршрута удобно использовать кастомный хук:
import { useState, useEffect } from 'haunted';
function useRoute() {
const [path, setPath] = useState(window.location.pathname);
useEffect(() => {
const onPopSt ate = () => setPath(window.location.pathname);
window.addEventListener('popstate', onPopState);
return () => window.removeEventListener('popstate', onPopState);
}, []);
const navigate = (to) => {
if (to !== path) {
window.history.pushState({}, '', to);
setPath(to);
}
};
return [path, navigate];
}
Ключевые моменты:
window.history.pushState позволяет изменить URL без
перезагрузки страницы.popstate срабатывает при нажатии кнопок
«назад»/«вперед» в браузере.После получения текущего пути можно динамически отображать компоненты:
import { html } from 'haunted';
function RouterComponent() {
const [path, navigate] = useRoute();
let view;
switch (path) {
case '/':
view = html`<home-page></home-page>`;
break;
case '/about':
view = html`<about-page></about-page>`;
break;
case '/contact':
view = html`<contact-page></contact-page>`;
break;
default:
view = html`<not-found-page></not-found-page>`;
}
return html`
<nav>
<a href="/" @click=${(e) => { e.preventDefault(); navigate('/'); }}>Главная</a>
<a href="/about" @click=${(e) => { e.preventDefault(); navigate('/about'); }}>О нас</a>
<a href="/contact" @click=${(e) => { e.preventDefault(); navigate('/contact'); }}>Контакты</a>
</nav>
<main>${view}</main>
`;
}
Особенности реализации:
@click для перехвата клика и
предотвращения стандартного перехода браузера.switch по
текущему пути.Для маршрутов с параметрами (/user/:id) создается
простая функция для извлечения параметров:
function matchRoute(path, pattern) {
const pathParts = path.split('/').filter(Boolean);
const patternParts = pattern.split('/').filter(Boolean);
if (pathParts.length !== patternParts.length) return null;
const params = {};
for (let i = 0; i < patternParts.length; i++) {
if (patternParts[i].startsWith(':')) {
params[patternParts[i].slice(1)] = pathParts[i];
} else if (patternParts[i] !== pathParts[i]) {
return null;
}
}
return params;
}
Пример использования:
const params = matchRoute('/user/42', '/user/:id');
console.log(params); // { id: "42" }
Такой подход позволяет строить сложные маршруты без сторонних библиотек.
Чтобы SPA корректно обрабатывала переходы и обновления страницы, рекомендуется использовать:
window.history.replaceState для начальной инициализации
маршрута.state
объекта истории.Пример интеграции с хуком useEffect:
useEffect(() => {
document.title = path === '/' ? 'Главная' : `Страница: ${path}`;
window.history.replaceState({ path }, '');
}, [path]);
Пример вложенной маршрутизации:
if (path.startsWith('/dashboard')) {
view = html`<dashboard-page subpath=${path.replace('/dashboard', '')}></dashboard-page>`;
}
Внутри dashboard-page можно повторять логику
маршрутизации для внутренних секций.
Для проектов, требующих более полного решения маршрутизации, Haunted легко интегрируется с Vaadin Router:
import { Router } from '@vaadin/router';
const outlet = document.getElementById('app');
const router = new Router(outlet);
router.setRoutes([
{ path: '/', component: 'home-page' },
{ path: '/about', component: 'about-page' },
{ path: '/user/:id', component: 'user-page' },
]);
component — Web Component, созданный через
Haunted.popstate, так и интегрироваться с полноценными
маршрутизаторами.Такой подход сохраняет легковесность приложений на Haunted и обеспечивает гибкость управления URL и интерфейсом.