Клиентская навигация

SkateJS — это легковесная библиотека для создания веб-компонентов с использованием современных возможностей браузера и JavaScript. В контексте клиентской навигации она позволяет организовывать маршрутизацию и управление состоянием страниц без перезагрузки браузера, используя преимущества Shadow DOM и реактивных свойств компонентов.


Роутинг и управление состоянием

В SkateJS нет встроенного роутера, как в Angular или Vue, но его можно реализовать с помощью комбинации Custom Elements, событий DOM и наблюдаемых свойств. Основная идея — хранить текущий путь в компоненте, отслеживать его изменения и отображать соответствующий контент.

import { define, props, h } from 'skatejs';

class Router extends HTMLElement {
  static props = {
    path: props.string({ attribute: true })
  };

  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    window.addEventListener('popstate', () => {
      this.path = window.location.pathname;
    });
  }

  render() {
    switch (this.path) {
      case '/about':
        return h('about-page', {});
      case '/contact':
        return h('contact-page', {});
      default:
        return h('home-page', {});
    }
  }
}

define(Router, 'app-router');

Ключевые моменты:

  • window.history.pushState и window.onpopstate используются для управления историей браузера без перезагрузки страницы.
  • Свойство path наблюдается SkateJS, и при изменении автоматически происходит повторный рендер.
  • Использование switch или Map позволяет легко расширять маршруты.

Навигационные ссылки

Для интеграции маршрутов с элементами страницы удобно создать специальный компонент ссылки, который перехватывает клики и обновляет путь без полной перезагрузки:

class RouterLink extends HTMLElement {
  connectedCallback() {
    this.addEventListener('click', e => {
      e.preventDefault();
      const href = this.getAttribute('href');
      history.pushState({}, '', href);
      window.dispatchEvent(new Event('popstate'));
    });
  }
}

define(RouterLink, 'router-link');

Особенности реализации:

  • Перехват события click предотвращает стандартное поведение браузера.
  • Событие popstate уведомляет роутер о смене пути.
  • Такой подход позволяет использовать SEO-дружественные ссылки и поддерживать «кнопку назад» в браузере.

Работа с динамическими маршрутами

Для более сложных приложений необходимо поддерживать динамические сегменты URL, например /users/:id. Реализовать это можно с помощью регулярных выражений и функции сопоставления:

function matchRoute(path, routePattern) {
  const regex = new RegExp('^' + routePattern.replace(/:\w+/g, '([^/]+)') + '$');
  const match = path.match(regex);
  if (!match) return null;
  const keys = (routePattern.match(/:\w+/g) || []).map(k => k.substring(1));
  const params = {};
  keys.forEach((key, i) => params[key] = match[i + 1]);
  return params;
}

const params = matchRoute('/users/42', '/users/:id'); // { id: "42" }

Принципы:

  • Поддержка параметров позволяет строить маршруты для любых сущностей.
  • Совмещение с рендером SkateJS обеспечивает реактивное отображение страниц с данными, полученными по параметрам.

Асинхронная загрузка компонентов

Для крупных приложений важно минимизировать загрузку кода. SkateJS поддерживает динамический импорт компонентов:

async function loadPage(path) {
  switch(path) {
    case '/about':
      await import('./about-page.js');
      return h('about-page', {});
    case '/contact':
      await import('./contact-page.js');
      return h('contact-page', {});
    default:
      await import('./home-page.js');
      return h('home-page', {});
  }
}

Преимущества:

  • Экономия трафика за счет ленивой загрузки компонентов.
  • Компоненты рендерятся только при необходимости.
  • Возможность сочетания с async/await и промисами для загрузки данных перед рендером.

Управление состоянием навигации

Важной частью клиентской навигации является синхронизация состояния интерфейса с URL. SkateJS позволяет хранить состояние в свойствах компонентов и использовать его для условного рендеринга:

class TabsComponent extends HTMLElement {
  static props = {
    activeTab: props.string()
  };

  render() {
    return h('div', {}, [
      h('nav', {}, [
        h('router-link', { href: '/tab1' }, 'Tab 1'),
        h('router-link', { href: '/tab2' }, 'Tab 2')
      ]),
      h('section', {}, [
        this.activeTab === 'tab1' ? h('tab1-component') : h('tab2-component')
      ])
    ]);
  }
}

Рекомендации:

  • Для сложных состояний можно использовать объект state, наблюдаемый через SkateJS props.
  • Связывание свойств с URL позволяет реализовать «сохранение состояния при перезагрузке».

События и взаимодействие компонентов

SkateJS поддерживает событие CustomEvent, что удобно для передачи информации между роутером и дочерними компонентами:

this.dispatchEvent(new CustomEvent('navigate', {
  detail: { path: '/contact' },
  bubbles: true,
  composed: true
}));

Преимущества:

  • События легко перехватывать на родительских компонентах.
  • Поддержка bubbles и composed обеспечивает взаимодействие с Shadow DOM.
  • Позволяет строить гибкую архитектуру с независимыми компонентами и общим роутером.

Итоговые рекомендации по архитектуре

  • Разделение роутера и страниц на отдельные компоненты обеспечивает модульность.
  • Использование реактивных свойств для отслеживания пути и состояния навигации упрощает управление.
  • Динамические маршруты и ленивые импорты компонентов повышают масштабируемость приложения.
  • События DOM позволяют интегрировать навигацию без сложных внешних библиотек.

Эти подходы формируют основу клиентской навигации в SkateJS, обеспечивая современное SPA-поведение с минимальными накладными расходами и максимальной гибкостью.