Клиентская маршрутизация

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


Создание реактивного состояния маршрута

Для маршрутизации важно хранить текущее состояние пути приложения. Чаще всего используют объект, который содержит текущий путь и параметры. В FAST Element это реализуется через реактивные свойства компонентов.

import { FASTElement, html, css, observable } from "@microsoft/fast-element";

class RouterState {
    @observable currentPath = window.location.pathname;

    navigate(path) {
        this.currentPath = path;
        window.history.pushState({}, "", path);
        window.dispatchEvent(new Event("popstate"));
    }
}

export const routerState = new RouterState();
  • @observable позволяет автоматически обновлять компоненты, которые зависят от currentPath.
  • Метод navigate изменяет путь без перезагрузки страницы и триггерит событие popstate для синхронизации.

Реактивный компонент маршрутизатора

Для управления отображением разных компонентов в зависимости от пути создается Router Component:

const routerTemplate = html`
    <template>
        ${x => {
            switch (x.path) {
                case "/":
                    return html`<home-page></home-page>`;
                case "/about":
                    return html`<about-page></about-page>`;
                default:
                    return html`<not-found-page></not-found-page>`;
            }
        }}
    </template>
`;

class AppRouter extends FASTElement {
    @observable path = routerState.currentPath;

    connectedCallback() {
        super.connectedCallback();
        this.handlePopState = () => this.path = routerState.currentPath;
        window.addEventListener("popstate", this.handlePopState);
    }

    disconnectedCallback() {
        super.disconnectedCallback();
        window.removeEventListener("popstate", this.handlePopState);
    }
}

AppRouter.define({
    name: "app-router",
    template: routerTemplate
});
  • Свойство path синхронизируется с routerState.
  • Компонент реагирует на изменения URL автоматически благодаря реактивности FAST Element.
  • connectedCallback и disconnectedCallback обеспечивают корректную регистрацию и удаление слушателей событий.

Декларативная навигация

Навигацию можно сделать через стандартные элементы <a> с обработкой события click:

const navTemplate = html`
    <nav>
        <a href="/" @click=${x => x.navigate('/')}>Главная</a>
        <a href="/about" @click=${x => x.navigate('/about')}>О проекте</a>
    </nav>
`;

class AppNav extends FASTElement {
    navigate(path) {
        event.preventDefault();
        routerState.navigate(path);
    }
}

AppNav.define({
    name: "app-nav",
    template: navTemplate
});
  • event.preventDefault() предотвращает стандартную перезагрузку страницы.
  • Использование реактивного состояния routerState гарантирует синхронизацию с маршрутизатором.

Обработка параметров маршрута

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

class Route {
    constructor(pattern, component) {
        this.pattern = pattern;
        this.component = component;
    }

    match(path) {
        const regex = new RegExp(this.pattern.replace(/:\w+/g, "([^/]+)"));
        const result = path.match(regex);
        if (!result) return null;
        const keys = [...this.pattern.matchAll(/:(\w+)/g)].map(m => m[1]);
        const params = Object.fromEntries(keys.map((k, i) => [k, result[i + 1]]));
        return params;
    }
}
  • pattern содержит путь с параметрами.
  • Метод match возвращает объект параметров или null, если путь не подходит.
  • Позволяет строить гибкую маршрутизацию с динамическими сегментами URL.

Поддержка вложенных маршрутов

FAST Element позволяет создавать вложенные маршруты с помощью компонентов внутри компонентов:

const userTemplate = html`
    <template>
        <h2>Профиль пользователя: ${x => x.userId}</h2>
        <app-router></app-router>
    </template>
`;

class UserPage extends FASTElement {
    @observable userId;

    connectedCallback() {
        super.connectedCallback();
        const params = routerState.currentPath.match(/\/user\/(\d+)/);
        this.userId = params ? params[1] : null;
    }
}

UserPage.define({
    name: "user-page",
    template: userTemplate
});
  • Вложенные маршруты позволяют разделять ответственность компонентов.
  • Реактивные свойства автоматически обновляются при смене URL.

Управление состоянием истории браузера

FAST Element работает с window.history и popstate для синхронизации состояния. Важно учитывать:

  • history.pushState добавляет запись в историю без перезагрузки страницы.
  • history.replaceState заменяет текущую запись.
  • Обработка события popstate гарантирует реактивное обновление компонентов при навигации назад/вперед.

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

Для крупных приложений можно подгружать страницы динамически с помощью import():

async function loadComponent(path) {
    switch (path) {
        case "/about":
            await import("./about-page.js");
            break;
        case "/user":
            await import("./user-page.js");
            break;
    }
    routerState.currentPath = path;
}
  • Асинхронная загрузка сокращает время первой загрузки приложения.
  • В сочетании с реактивным маршрутизатором новые компоненты рендерятся автоматически после загрузки.

Интеграция с FAST Element: реактивность и шаблоны

Ключевое преимущество FAST Element — реактивность на уровне свойств и данных. Это позволяет:

  • Связывать текущее состояние маршрута с шаблонами через ${x => x.path}.
  • Автоматически обновлять DOM без ручного управления элементами.
  • Создавать декларативные маршруты без дополнительного роутингового фреймворка.

Практические рекомендации

  • Использовать один глобальный объект состояния routerState.
  • Разделять маршруты по компонентам для поддерживаемости.
  • Обрабатывать неизвестные пути через компонент NotFound.
  • Динамические параметры URL извлекать через регулярные выражения.
  • Подгружать тяжелые страницы асинхронно для ускорения загрузки приложения.