Кастомные резолверы

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

Резолвер — это функция с определённой сигнатурой:

async function customResolver(route, context, next) {
    // route — текущий маршрут
    // context — объект контекста маршрутизации
    // next — функция для передачи управления следующему резолверу
}
  • route — объект маршрута с его свойствами: path, action, children, name.
  • context — объект, который передается в router.resolve(context) и доступен в каждом резолвере.
  • next — функция для вызова следующего резолвера в цепочке, позволяет строить цепочку обработки.

Резолвер должен вернуть результат маршрутизации, либо вызвать next() для продолжения поиска.


Создание простого кастомного резолвера

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

import UniversalRouter from 'universal-router';

const routes = [
  { path: '/', action: () => 'Главная' },
  { path: '/about', action: () => 'О нас' },
];

const loggerResolver = async (route, context, next) => {
  console.log(`Попытка обработки маршрута: ${route.path}`);
  const result = await next();
  console.log(`Результат маршрута: ${result}`);
  return result;
};

const router = new UniversalRouter(routes, {
  resolveRoute: loggerResolver
});

router.resolve({ pathname: '/' }).then(console.log);

В данном примере loggerResolver выполняет логирование до и после обработки маршрута. Функция next() гарантирует, что стандартный обработчик маршрута будет вызван после логирования.


Асинхронные операции внутри резолвера

Кастомные резолверы идеально подходят для интеграции асинхронной логики: загрузки данных, проверки прав доступа, выполнения API-запросов.

const authResolver = async (route, context, next) => {
  if (route.meta?.requiresAuth && !context.user) {
    return '/login'; // перенаправление на страницу логина
  }
  return next(); // продолжаем стандартную обработку
};

const router = new UniversalRouter(routes, {
  resolveRoute: authResolver
});

Здесь резолвер проверяет наличие пользователя в контексте и при необходимости перенаправляет на страницу логина. Асинхронная проверка возможна, если, например, получать пользователя через API:

const asyncAuthResolver = async (route, context, next) => {
  context.user = await fetchUserFromAPI();
  if (route.meta?.requiresAuth && !context.user) return '/login';
  return next();
};

Цепочка резолверов

Universal Router позволяет использовать цепочку резолверов: первый резолвер может обработать часть логики, второй — другую. Это достигается вызовом next():

const resolver1 = async (route, context, next) => {
  console.log('Резолвер 1');
  return next();
};

const resolver2 = async (route, context, next) => {
  console.log('Резолвер 2');
  return next();
};

const router = new UniversalRouter(routes, {
  resolveRoute: async (route, context, next) => resolver1(route, context, () => resolver2(route, context, next))
});

Цепочка позволяет разделять ответственность: один резолвер занимается авторизацией, другой — логированием, третий — загрузкой данных.


Динамическое разрешение маршрутов

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

const dynamicResolver = async (route, context, next) => {
  if (!route.childrenLoaded && route.loadChildren) {
    route.children = await route.loadChildren();
    route.childrenLoaded = true;
  }
  return next();
};

const routes = [
  {
    path: '/dashboard',
    loadChildren: async () => [
      { path: '/stats', action: () => 'Статистика' },
      { path: '/settings', action: () => 'Настройки' },
    ]
  }
];

const router = new UniversalRouter(routes, { resolveRoute: dynamicResolver });

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


Использование контекста в кастомных резолверах

Контекст (context) передаётся при вызове router.resolve(context) и доступен во всех резолверах. Его можно использовать для передачи состояния, например, данных пользователя, конфигурации приложения или маршрута перенаправления.

const contextResolver = async (route, context, next) => {
  context.startTime = Date.now();
  const result = await next();
  console.log(`Время обработки маршрута: ${Date.now() - context.startTime}мс`);
  return result;
};

router.resolve({ pathname: '/about', user: { id: 1 } }).then(console.log);

Обработка ошибок в резолверах

Кастомные резолверы могут ловить ошибки отдельных маршрутов и возвращать альтернативный результат или перенаправление:

const errorHandlingResolver = async (route, context, next) => {
  try {
    return await next();
  } catch (err) {
    console.error(`Ошибка маршрута ${route.path}:`, err);
    return '/error';
  }
};

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


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

  • Для асинхронных операций использовать async/await внутри резолверов.
  • Разделять резолверы по ответственности, создавая цепочки.
  • Использовать контекст для передачи глобальных данных.
  • В динамических приложениях применять ленивую загрузку маршрутов через кастомные резолверы.
  • Всегда вызывать next() для продолжения поиска маршрута, если логика резолвера не завершает обработку полностью.

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