Haunted — это библиотека для создания реактивных веб-компонентов на базе стандартов Web Components и Hooks, вдохновлённых React. Одной из ключевых возможностей при построении SPA (Single Page Application) является управление маршрутизацией. В сложных приложениях часто возникает необходимость вложенных маршрутов, когда один компонент маршрута содержит внутри себя несколько других маршрутов.
Haunted не предоставляет встроенной маршрутизации, но хорошо
сочетается с популярными решениями вроде vaadin-router или
wouter. Вложенные маршруты обычно строятся с использованием
дочерних <router-outlet> компонентов
или аналогичных контейнеров, которые динамически меняют содержимое в
зависимости от состояния маршрута.
Пример базового подхода:
import { html, component } from 'haunted';
import { Router } from '@vaadin/router';
function app() {
return html`
<header>
<a href="/">Главная</a>
<a href="/dashboard">Панель</a>
</header>
<main id="outlet"></main>
`;
}
customElements.define('my-app', component(app));
Настройка маршрутов:
const router = new Router(document.getElementById('outlet'));
router.setRoutes([
{ path: '/', component: 'home-page' },
{ path: '/dashboard/*', component: 'dashboard-page' },
]);
Ключевая идея вложенных маршрутов — внутри родительского компонента создаётся собственный роутер, который управляет отображением дочерних компонентов.
Пример компонента с вложенными маршрутами:
function dashboardPage() {
const outletRef = document.createElement('div');
outletRef.id = 'dashboard-outlet';
haunted.useEffect(() => {
const nestedRouter = new Router(outletRef);
nestedRouter.setRoutes([
{ path: '/dashboard/overview', component: 'dashboard-overview' },
{ path: '/dashboard/settings', component: 'dashboard-settings' },
]);
}, []);
return html`
<nav>
<a href="/dashboard/overview">Обзор</a>
<a href="/dashboard/settings">Настройки</a>
</nav>
<div id="dashboard-outlet"></div>
`;
}
customElements.define('dashboard-page', component(dashboardPage));
Особенности:
useEffect позволяет инициализировать роутер только
после того, как элемент появился в DOM./dashboard/*) должен заканчиваться
звездочкой *, чтобы маршруты дочернего уровня корректно
обрабатывались.Часто необходимо, чтобы родительский компонент мог передавать данные в дочерние маршруты. Это достигается через состояния Haunted:
import { useState } from 'haunted';
function dashboardPage() {
const [user, setUser] = useState({ name: 'Alex' });
const outletRef = document.createElement('div');
haunted.useEffect(() => {
const nestedRouter = new Router(outletRef);
nestedRouter.setRoutes([
{
path: '/dashboard/overview',
component: 'dashboard-overview',
action: (_context, _commands) => {
outletRef.querySelector('dashboard-overview')?.setUser(user);
}
},
{ path: '/dashboard/settings', component: 'dashboard-settings' },
]);
}, []);
return html`
<nav>
<a href="/dashboard/overview">Обзор</a>
<a href="/dashboard/settings">Настройки</a>
</nav>
<div id="dashboard-outlet"></div>
`;
}
В дочернем компоненте:
function dashboardOverview() {
let user;
const setUser = (newUser) => {
user = newUser;
haunted.render(thisTemplate(), this);
};
const thisTemplate = () => html`<p>Привет, ${user.name}</p>`;
return thisTemplate();
}
customElements.define('dashboard-overview', component(dashboardOverview));
Таким образом можно управлять состоянием на уровне родителя, при этом дочерние компоненты остаются реактивными и поддерживают собственные обновления.
Для сложных приложений часто требуется динамически создавать маршруты, например, на основе данных с сервера. В Haunted это реализуется через динамическую генерацию конфигурации маршрутов:
const nestedRoutes = fetchedData.map(item => ({
path: `/dashboard/item/${item.id}`,
component: 'dashboard-item',
data: item
}));
const nestedRouter = new Router(document.getElementById('dashboard-outlet'));
nestedRouter.setRoutes(nestedRoutes);
В дочернем компоненте можно получать данные через атрибуты:
function dashboardItem() {
let itemData;
haunted.useEffect(() => {
itemData = this.getAttribute('data-item');
}, []);
return html`<p>Элемент: ${itemData.name}</p>`;
}
customElements.define('dashboard-item', component(dashboardItem));
*: это важно
для корректной работы дочерних маршрутов.Вложенные маршруты позволяют строить сложные, многоуровневые интерфейсы с полностью реактивными компонентами, сохраняя контроль над состоянием и навигацией на каждом уровне. Правильная организация роутеров и управление состоянием делают архитектуру приложения прозрачной и удобной для масштабирования.