Организация иерархии путей

Page.js — это небольшая клиентская библиотека для управления маршрутизацией в одностраничных приложениях (SPA) на JavaScript. Важным аспектом при работе с Page.js является правильная организация иерархии путей. Это позволяет создавать масштабируемые и легко поддерживаемые приложения.

Маршруты в Page.js определяются функцией page(path, callback). Здесь path — это строка маршрута, а callback — функция, которая выполняется при переходе на указанный путь.

Пример базового маршрута:

page('/about', () => {
  console.log('Страница "О проекте"');
});

Вложенные маршруты

Для организации вложенной структуры страниц используется определение маршрутов с общим префиксом. Это позволяет создавать иерархические структуры, где родительский маршрут задаёт контекст, а дочерние маршруты уточняют содержание.

Пример:

page('/dashboard', () => {
  console.log('Главная панель');
});

page('/dashboard/settings', () => {
  console.log('Настройки панели');
});

page('/dashboard/profile', () => {
  console.log('Профиль пользователя');
});

В этом примере /dashboard является родительским маршрутом, а /dashboard/settings и /dashboard/profile — дочерними. Такой подход упрощает добавление новых разделов и поддержание согласованности URL.

Параметры маршрутов

Page.js поддерживает динамические сегменты, которые позволяют создавать маршруты с параметрами. Параметры задаются через двоеточие (:), и значения параметров автоматически передаются в объект ctx.params внутри callback-функции.

Пример:

page('/users/:userId', (ctx) => {
  console.log(`ID пользователя: ${ctx.params.userId}`);
});

page('/users/:userId/posts/:postId', (ctx) => {
  console.log(`Пользователь ${ctx.params.userId}, пост ${ctx.params.postId}`);
});

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

Middleware и иерархия маршрутов

Page.js позволяет использовать middleware-функции для обработки маршрутов до вызова основного callback. Middleware удобно применять для реализации авторизации, загрузки данных или общего поведения для группы маршрутов.

Пример с middleware для всех маршрутов внутри /dashboard:

function requireAuth(ctx, next) {
  if (!ctx.user) {
    page.redirect('/login');
  } else {
    next();
  }
}

page('/dashboard*', requireAuth); // звездочка '*' позволяет применить middleware ко всем дочерним маршрутам
page('/dashboard', () => console.log('Главная панель'));
page('/dashboard/settings', () => console.log('Настройки'));

Использование префикса с * — мощный инструмент для группировки маршрутов и реализации общей логики для всего раздела.

Вложенные middleware

Вложенные маршруты могут иметь собственные middleware, которые будут вызываться после middleware родителя. Это позволяет строить многоуровневую обработку запросов.

page('/dashboard', requireAuth, (ctx, next) => {
  console.log('Главная панель');
  next();
});

function settingsMiddleware(ctx, next) {
  console.log('Загружаем настройки');
  next();
}

page('/dashboard/settings', settingsMiddleware, (ctx) => {
  console.log('Страница настроек');
});

Таким образом, для маршрута /dashboard/settings последовательно вызываются requireAuth, settingsMiddleware и основной callback.

Структура маршрутов через массивы

Для удобства и удобочитаемости большие приложения могут строить маршруты через массивы объектов:

const routes = [
  { path: '/', callback: () => console.log('Главная') },
  { path: '/about', callback: () => console.log('О проекте') },
  {
    path: '/dashboard',
    middleware: [requireAuth],
    children: [
      { path: '', callback: () => console.log('Панель') },
      { path: '/settings', callback: () => console.log('Настройки') },
      { path: '/profile', callback: () => console.log('Профиль') },
    ]
  }
];

function registerRoutes(routeArray, parent = '') {
  routeArray.forEach(route => {
    const fullPath = parent + route.path;
    const handlers = [...(route.middleware || []), route.callback];
    page(fullPath, ...handlers);
    if (route.children) {
      registerRoutes(route.children, fullPath);
    }
  });
}

registerRoutes(routes);

Такой подход упрощает масштабирование, добавление новых маршрутов и поддержание иерархии.

Использование wildcard и catch-all маршрутов

Для обработки несуществующих страниц или динамических путей используется символ * в конце маршрута:

page('*', () => {
  console.log('Страница не найдена');
});

Также можно комбинировать динамические параметры и wildcard:

page('/blog/:slug*', (ctx) => {
  console.log(`Статья: ${ctx.params.slug}`);
});

Рекомендации по организации иерархии

  • Разделять маршруты на родительские и дочерние для отражения логики приложения.
  • Использовать middleware для общих операций на уровне группы маршрутов.
  • Применять массивы объектов и рекурсивную регистрацию маршрутов для крупных проектов.
  • Вложенные маршруты должны логически наследовать контекст родителя, например /dashboard/settings всегда относится к /dashboard.
  • Динамические сегменты и wildcard маршруты помогают покрыть все возможные варианты URL, сохраняя иерархическую структуру.

Иерархическая организация маршрутов в Page.js повышает читаемость кода, упрощает поддержку и расширение приложения, а также обеспечивает централизованное управление логикой для группы связанных страниц.