Порядок выполнения middleware

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


Последовательность выполнения

  1. Сопоставление маршрута (route matching) Universal Router сначала пытается найти маршрут, соответствующий текущему URL. Алгоритм поиска происходит сверху вниз по списку маршрутов. Каждый маршрут проверяется на соответствие шаблону path. Если маршрут содержит вложенные маршруты (children), проверка продолжается рекурсивно.

    Особенность: маршруты с более точным соответствием имеют приоритет перед маршрутами с параметрами или wildcard (*).

  2. Вызов middleware через beforeEnter или resolve Для каждого маршрута можно определить функцию beforeEnter(context, next), которая выполняется перед основным обработчиком маршрута. Эта функция может:

    • Перенаправлять пользователя на другой маршрут через return router.resolve('/new-path');
    • Возвращать промис для асинхронной проверки;
    • Вызывать next() для продолжения выполнения цепочки.

    Если beforeEnter возвращает значение, оно прерывает стандартный вызов resolve для этого маршрута и используется как результат маршрутизации.

  3. Обработка основного resolve маршрута Основная функция resolve(context, params) выполняется после прохождения всех beforeEnter. Здесь обрабатывается:

    • Получение данных для рендера страницы;
    • Формирование объекта ответа (HTML, JSON и т.д.);
    • Вызов вложенных маршрутов, если они существуют и соответствуют текущему URL.

    Важно: если resolve возвращает промис, Universal Router дожидается его выполнения, что позволяет использовать асинхронные операции, такие как загрузка данных с сервера.

  4. Вызов вложенных маршрутов (children) После выполнения resolve маршрута Universal Router проверяет наличие дочерних маршрутов в свойстве children. Порядок выполнения дочерних маршрутов аналогичен: сначала beforeEnter, затем resolve.

    Принцип каскада: родительский маршрут должен завершить свои middleware до того, как будут вызваны middleware дочерних маршрутов. Это обеспечивает возможность установки контекста (context) для всех вложенных обработчиков.

  5. Middleware глобального уровня Помимо middleware на уровне отдельных маршрутов, Universal Router позволяет реализовать глобальные обработчики, например через обертку маршрутизатора:

    const router = new UniversalRouter(routes, {
      resolveRoute(context, route) {
        // глобальная логика перед вызовом route.resolve
        if (route.resolve) return route.resolve(context, route.params);
      }
    });

    Глобальные middleware выполняются до вызова конкретного resolve маршрута, что позволяет реализовать универсальные проверки (авторизация, логирование).


Важные нюансы порядка

  • Асинхронность: Каждый middleware может быть асинхронным. Universal Router корректно обрабатывает промисы, ожидая их завершения перед переходом к следующему уровню.
  • Прерывание цепочки: Возврат значения из beforeEnter или resolve маршрута может полностью остановить дальнейшее выполнение, включая дочерние маршруты.
  • Переадресация: Вызов router.resolve('/another') внутри middleware создает новый цикл маршрутизации, который также учитывает порядок beforeEnter и resolve.
  • Объединение контекста: Контекст context передается через всю цепочку middleware и может модифицироваться на каждом уровне, что обеспечивает гибкость передачи данных между родительскими и дочерними маршрутами.

Рекомендации по структурированию middleware

  • Разделение ответственности: Каждый middleware должен выполнять одну задачу (например, проверку авторизации, логирование или загрузку данных).
  • Минимизация блокирующих операций: Асинхронные операции должны быть оптимизированы, чтобы не задерживать цепочку выполнения.
  • Использование вложенных маршрутов для локального middleware: Для страниц с общим функционалом лучше использовать родительский маршрут с beforeEnter, чтобы дочерние маршруты автоматически наследовали проверку.
  • Явная обработка ошибок: Любой промис в middleware должен иметь обработку ошибок, чтобы исключения не прерывали маршрутизацию неожиданно.

Пример полного порядка выполнения

const routes = [
  {
    path: '/dashboard',
    beforeEnter: async (ctx, next) => {
      if (!ctx.user) return '/login';
      return next();
    },
    resolve: async (ctx) => {
      const data = await fetchDashboardData(ctx.user.id);
      return { template: 'dashboard', data };
    },
    children: [
      {
        path: 'settings',
        resolve: (ctx) => ({ template: 'settings', data: ctx.user.settings })
      }
    ]
  }
];

Порядок вызова для /dashboard/settings:

  1. beforeEnter родительского маршрута /dashboard
  2. resolve родительского маршрута /dashboard (загрузка данных дашборда)
  3. Проверка дочернего маршрута settings
  4. resolve дочернего маршрута settings

Контекст ctx передается на каждом шаге и может использоваться всеми уровнями маршрутов.


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