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 уведомляет роутер о смене пути.Для более сложных приложений необходимо поддерживать динамические
сегменты 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 поддерживает динамический импорт компонентов:
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.SkateJS поддерживает событие CustomEvent, что удобно для
передачи информации между роутером и дочерними компонентами:
this.dispatchEvent(new CustomEvent('navigate', {
detail: { path: '/contact' },
bubbles: true,
composed: true
}));
Преимущества:
bubbles и composed обеспечивает
взаимодействие с Shadow DOM.Эти подходы формируют основу клиентской навигации в SkateJS, обеспечивая современное SPA-поведение с минимальными накладными расходами и максимальной гибкостью.