Программная навигация

SkateJS — это библиотека для построения веб-компонентов на основе стандарта Custom Elements с упором на реактивность, простоту и переиспользуемость. В основе лежит концепция компонентов как объектов с жизненным циклом, что позволяет структурировать интерфейс в виде независимых и легко тестируемых модулей.

Компонент создается с помощью функции skate и описывается через объект с ключевыми свойствами:

  • props — определяет свойства компонента с типами и значениями по умолчанию.
  • render — функция рендеринга, возвращающая разметку (обычно через JSX или шаблонную строку).
  • connected / disconnected — методы жизненного цикла, вызываемые при добавлении или удалении элемента из DOM.
  • updated — метод, вызываемый при изменении свойств компонента.

Пример минимального компонента:

import { skate } from 'skatejs';

skate('my-counter', {
  props: {
    count: { type: Number, default: 0 }
  },
  render({ count }) {
    return `<div>Счетчик: ${count}</div>`;
  },
  connected() {
    console.log('Компонент добавлен в DOM');
  },
  updated(prev, next) {
    console.log(`Счетчик изменился с ${prev.count} на ${next.count}`);
  }
});

Программная навигация

Программная навигация в контексте SkateJS — это управление состоянием и переходами между страницами или состояниями интерфейса без полной перезагрузки документа. В современных приложениях чаще всего реализуется через Router-компоненты, которые слушают изменения URL или внутреннего состояния и рендерят соответствующие компоненты.

Реактивное состояние маршрутов

Состояние маршрутизации обычно хранится в виде объекта с ключами:

const routes = {
  '/': HomeComponent,
  '/about': AboutComponent,
  '/profile/:id': ProfileComponent
};

SkateJS позволяет связать состояние маршрута с компонентами через реактивные свойства и методы жизненного цикла. При изменении URL можно программно обновить свойство currentRoute компонента-роутера:

skate('app-router', {
  props: {
    currentRoute: { type: String, default: '/' }
  },
  render({ currentRoute }) {
    const Component = routes[currentRoute] || NotFoundComponent;
    return `<${Component}></${Component}>`;
  }
});

Обработка параметров URL

Для динамических маршрутов (/profile/:id) важно уметь извлекать параметры. В SkateJS это достигается через парсинг URL и реактивное обновление свойств:

function parseRoute(url) {
  const match = url.match(/\/profile\/(\d+)/);
  if (match) {
    return { route: '/profile/:id', id: match[1] };
  }
  return { route: url };
}

skate('app-router', {
  props: {
    currentRoute: { type: String, default: '/' },
    routeParams: { type: Object, default: () => ({}) }
  },
  connected() {
    window.addEventListener('popstate', this.onRouteChange.bind(this));
    this.onRouteChange();
  },
  disconnected() {
    window.removeEventListener('popstate', this.onRouteChange.bind(this));
  },
  onRouteChange() {
    const { route, id } = parseRoute(window.location.pathname);
    this.currentRoute = route;
    this.routeParams = id ? { id } : {};
  },
  render({ currentRoute, routeParams }) {
    const Component = routes[currentRoute] || NotFoundComponent;
    return `<${Component} .params=${JSON.stringify(routeParams)}></${Component}>`;
  }
});

Программные переходы

Для навигации без перезагрузки страницы используется метод history.pushState или history.replaceState. Skate-компонент роутера реагирует на эти изменения через popstate:

function navigateTo(path) {
  history.pushState({}, '', path);
  window.dispatchEvent(new Event('popstate'));
}

// Пример кнопки навигации внутри компонента
skate('nav-link', {
  props: { href: String },
  render({ href }) {
    return `<a href="${href}" oncl ick="event.preventDefault(); navigateTo('${href}')">Перейти</a>`;
  }
});

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

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

const routes = {
  '/': () => import('./HomeComponent.js'),
  '/about': () => import('./AboutComponent.js')
};

skate('app-router', {
  props: { currentRoute: String },
  async render({ currentRoute }) {
    const loader = routes[currentRoute];
    if (!loader) return '<div>Страница не найдена</div>';
    const module = await loader();
    const Component = module.default;
    return `<${Component}></${Component}>`;
  }
});

Такой подход позволяет сочетать реактивность SkateJS и lazy-loading, обеспечивая оптимальную производительность и гибкость навигации.


Интеграция с состоянием приложения

Для полноценной SPA-навигации важно синхронизировать маршруты с глобальным состоянием. SkateJS легко интегрируется с Redux, MobX или Zustand. Свойства компонента могут быть обертками над состоянием, автоматически вызывающими render при изменении:

import { state } from './store.js';

skate('app-router', {
  props: { currentRoute: String },
  render() {
    const current = state.route;
    const Component = routes[current] || NotFoundComponent;
    return `<${Component}></${Component}>`;
  }
});

Любое изменение state.route мгновенно обновляет интерфейс без дополнительных действий, сохраняя реактивность и предсказуемость.


Работа с историей и анимациями переходов

Для более сложных интерфейсов можно управлять анимацией переходов между маршрутами через CSS или Web Animations API, используя методы жизненного цикла connected, disconnected и updated:

skate('page-transition', {
  render() {
    return `<slot></slot>`;
  },
  updated(prev, next) {
    const el = this.shadowRoot.querySelector('slot');
    if (prev.currentRoute !== next.currentRoute) {
      el.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 300 });
    }
  }
});

Такой подход позволяет объединить программную навигацию и визуальные эффекты, сохраняя модульность компонентов.


SkateJS предоставляет полный набор инструментов для построения реактивной, управляемой программно навигации, где каждый компонент становится автономным, а состояние интерфейса и URL синхронизированы без лишних перезагрузок страницы.