Концепция вложенности

Universal Router — это легковесная библиотека маршрутизации для JavaScript, ориентированная на создание гибких и динамичных маршрутов. Одной из ключевых возможностей является поддержка вложенных маршрутов, что позволяет строить сложные структуры приложений с многоуровневыми путями.

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

import UniversalRouter from 'universal-router';

const routes = [
  {
    path: '/dashboard',
    action: () => 'Главная панель',
    children: [
      {
        path: '/stats',
        action: () => 'Статистика',
      },
      {
        path: '/settings',
        action: () => 'Настройки',
      },
    ],
  },
];

В приведенном примере путь /dashboard/stats автоматически сопоставляется с дочерним маршрутом, а Universal Router последовательно проверяет все уровни вложенности.


Принцип работы вложенных маршрутов

  1. Иерархия маршрутов — каждый маршрут может быть родительским или дочерним. Родительский маршрут отвечает за общий путь и может содержать общую логику, например, проверку авторизации или предварительную загрузку данных.
  2. Последовательное выполнение action — при совпадении маршрута выполняется функция action родителя, а затем дочернего маршрута, если это предусмотрено конфигурацией.
  3. Наследование параметров — дочерние маршруты автоматически получают параметры родительского пути. Это особенно важно для динамических сегментов:
const routes = [
  {
    path: '/user/:id',
    action: ({ params }) => `Пользователь ${params.id}`,
    children: [
      {
        path: '/profile',
        action: ({ parentParams }) => `Профиль пользователя ${parentParams.id}`,
      },
    ],
  },
];

Здесь путь /user/42/profile передает параметр id от родителя к дочернему маршруту через parentParams.


Динамическая вложенность

Universal Router позволяет динамически генерировать дочерние маршруты на основе условий или данных, полученных в action родителя. Это удобно для приложений с изменяемой структурой, например, с меню, загружаемым с сервера.

const routes = [
  {
    path: '/projects',
    async action(context) {
      const projects = await fetch('/api/projects').then(res => res.json());
      return projects.map(project => ({
        path: `/${project.id}`,
        action: () => `Проект: ${project.name}`,
      }));
    },
  },
];

В этом примере дочерние маршруты создаются динамически для каждого проекта после загрузки данных.


Механизм контекста и parent

Для работы с вложенными маршрутами важно понимать объект context, который передается в action. Он содержит ключевые свойства:

  • pathname — текущий путь, который сопоставляется с маршрутом.
  • params — параметры текущего маршрута.
  • parent — объект родительского маршрута, что позволяет использовать данные или логику родителя в дочернем маршруте.
  • router — ссылка на сам роутер, что позволяет программно производить перенаправления или создавать дополнительные маршруты.

Пример использования parent:

const routes = [
  {
    path: '/blog',
    action: () => ({ section: 'Блог' }),
    children: [
      {
        path: '/:postId',
        action: ({ params, parent }) => `Раздел: ${parent.section}, пост ${params.postId}`,
      },
    ],
  },
];

Особенности вложенных маршрутов

  • Совпадение по пути происходит рекурсивно, начиная с корневого маршрута.
  • Поддержка async action позволяет загружать данные на любом уровне вложенности.
  • Комбинирование статических и динамических сегментов упрощает построение REST-подобных маршрутов.
  • Доступ к контексту родителя позволяет избегать дублирования логики, например, проверки авторизации или загрузки общих данных.

Практические советы по организации вложенных маршрутов

  1. Разделять маршруты на логические группы по функциональным блокам: dashboard, user, settings.
  2. Использовать динамическую генерацию дочерних маршрутов для ресурсов, данные о которых приходят с API.
  3. Сохранять единый контекст для родителя, чтобы не дублировать данные во всех дочерних маршрутах.
  4. Минимизировать глубину вложенности, чтобы маршруты оставались понятными и управляемыми.

Интеграция с другими библиотеками

Вложенные маршруты Universal Router удобно комбинировать с UI-фреймворками, такими как React или Vue. Чаще всего родительский маршрут отвечает за рендеринг общего шаблона (например, навигации), а дочерние маршруты — за конкретный контент.

import React from 'react';
import UniversalRouter from 'universal-router';
import { render } from 'react-dom';

const routes = [
  {
    path: '/app',
    action: () => <MainLayout />,
    children: [
      {
        path: '/home',
        action: () => <HomePage />,
      },
      {
        path: '/profile',
        action: () => <ProfilePage />,
      },
    ],
  },
];

const router = new UniversalRouter(routes);
router.resolve('/app/home').then(component => render(component, document.getElementById('root')));

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