Роутер в контексте

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

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.
  • Асинхронная подгрузка компонентов страниц.
  • Интеграция с CSS-анимациями для плавных переходов.

Такой подход позволяет строить сложные SPA на чистых веб-компонентах с минимальными зависимостями, сохраняя реактивность и модульность интерфейса.