Universal Router в JavaScript использует плоское дерево
маршрутов, что позволяет гибко сопоставлять пути с
обработчиками, но при этом при большом количестве маршрутов возникает
риск снижения производительности. Каждый маршрут представлен объектом с
обязательным свойством path и функцией action.
При запросе маршрута Universal Router последовательно проверяет
соответствие каждого пути.
Для повышения эффективности важно понимать, что стоимость поиска маршрута линейно зависит от числа маршрутов на верхнем уровне. Поэтому структурирование маршрутов в иерархию с вложенными подмаршрутами сокращает число проверок на каждом уровне.
const routes = [
{
path: '/users',
action: () => 'Users list',
children: [
{ path: '/:id', action: ({ params }) => `User ${params.id}` }
]
}
];
Здесь /users/:id обрабатывается внутри вложенного
маршрута, что снижает количество проверок для других топ-уровневых
маршрутов.
Universal Router позволяет создавать маршруты с отложенной
загрузкой обработчиков, что критически важно для крупных
приложений. Вместо того чтобы загружать все модули сразу,
action может возвращать Promise.
const routes = [
{
path: '/dashboard',
action: async () => {
const module = await import('./dashboard.js');
return module.default();
}
}
];
Такой подход уменьшает начальное время загрузки приложения и позволяет обрабатывать редко посещаемые маршруты только по мере необходимости.
Для ускорения разрешения путей можно использовать предварительное вычисление регулярных выражений. Universal Router автоматически компилирует строки пути в RegExp, но при большом количестве динамических сегментов это может быть узким местом.
import UniversalRouter from 'universal-router';
import { pathToRegexp } from 'path-to-regexp';
const precompiledRoutes = routes.map(route => ({
...route,
regexp: pathToRegexp(route.path)
}));
const router = new UniversalRouter(precompiledRoutes, {
resolveRoute(context, params) {
if (context.route.regexp.test(context.path)) {
return context.route.action({ params });
}
}
});
Предварительная компиляция позволяет избегать повторной генерации RegExp на каждом запросе, что особенно эффективно при множестве динамических маршрутов.
Для часто запрашиваемых путей имеет смысл использовать кэширование результатов маршрутизации. Например, можно хранить сопоставление URL → обработчик:
const cache = new Map();
async function resolvePath(path) {
if (cache.has(path)) return cache.get(path);
const result = await router.resolve(path);
cache.set(path, result);
return result;
}
Такой метод особенно полезен в приложениях с большим числом повторных навигаций, сокращая накладные расходы на повторное сопоставление.
Если дерево маршрутов содержит сотни элементов, имеет смысл
разделять маршруты по логическим признакам и выполнять
поиск только в нужной ветке. Например, разделение на admin,
user, public:
const routes = {
admin: [{ path: '/admin/dashboard', action: () => 'Admin dashboard' }],
user: [{ path: '/profile', action: () => 'User profile' }],
public: [{ path: '/', action: () => 'Home' }]
};
function resolveContext(type, path) {
return router.resolve({ routes: routes[type], path });
}
Это позволяет сократить количество проверок и избежать перебора ненужных маршрутов.
Вложенные маршруты позволяют создавать иерархию, но глубокие уровни увеличивают стоимость поиска. Чтобы минимизировать накладные расходы:
children только при реальной
необходимости: если маршруты не имеют общих предков,
вложенность не нужна.const routes = [
{
path: '/blog',
children: [
{ path: '/:year/:month/:slug', action: ({ params }) => `${params.slug}` }
]
}
];
Проверка сначала на /blog исключает ненужные
сопоставления для других топ-уровневых маршрутов.
*) маршрутов:
использование универсального сопоставления только там, где оно
необходимо.async action для ленивой загрузки.Эти подходы позволяют поддерживать высокую скорость маршрутизации даже при сотнях и тысячах маршрутов, обеспечивая масштабируемость приложений на Universal Router.