Universal Router — это гибкая библиотека маршрутизации для
JavaScript, которая позволяет организовывать навигацию в приложениях без
привязки к конкретному фреймворку. Основная единица маршрутизации —
маршрут, который задаётся объектом с ключами
path, action и опционально
children.
Пример базового маршрута:
import UniversalRouter from 'universal-router';
const routes = [
{
path: '/',
action: () => 'Главная страница',
},
{
path: '/about',
action: () => 'О нас',
},
];
const router = new UniversalRouter(routes);
router.resolve('/about').then(result => {
console.log(result); // "О нас"
});
Важным моментом является ленивая обработка
маршрутов: action вызывается только тогда, когда
маршрут совпадает, что уже снижает лишние вычисления.
Пересчёты или повторные вызовы обработчиков маршрутов могут значительно замедлять работу приложения, особенно если маршруты содержат тяжёлые операции (загрузка данных, вычисления, рендер компонентов). Основные подходы к оптимизации:
Если результат маршрута зависит только от URL и не меняется при повторных переходах, имеет смысл использовать кэширование:
const cache = new Map();
const routes = [
{
path: '/users/:id',
async action({ params }) {
const key = params.id;
if (cache.has(key)) return cache.get(key);
const data = await fetch(`/api/users/${key}`).then(res => res.json());
cache.set(key, data);
return data;
},
},
];
Преимущество: при повторном переходе к
/users/1 не выполняется запрос к серверу.
Важно: кэш нужно инвалидировать, если данные могут устареть.
children) для локализации
пересчетовUniversal Router поддерживает вложенные маршруты. Это позволяет разделить обработку и минимизировать пересчёты для неизменяемых сегментов:
const routes = [
{
path: '/dashboard',
children: [
{
path: '/analytics',
action: () => 'Аналитика',
},
{
path: '/settings',
action: () => 'Настройки',
},
],
},
];
При переходе внутри /dashboard пересчитываются только
дочерние маршруты, а родительский блок остаётся неизменным.
Ключевой момент: маршруты строятся и проверяются сверху вниз, поэтому вложенная структура сокращает повторные вычисления для неизменяемых участков интерфейса.
Universal Router позволяет строить маршруты динамически, но если структура известна заранее, стоит компилировать маршруты один раз:
const router = new UniversalRouter(routes, { resolveRoute: async (route, context) => {
// кастомная обработка
}});
Эффект: не создаётся новая логика при каждом вызове
resolve, уменьшая пересчёты путей и параметров.
Если часть маршрута требует тяжёлых операций (загрузка компонентов или данных), лучше вызывать их только при необходимости:
{
path: '/reports',
async action() {
const { ReportsPage } = await import('./ReportsPage.js');
return ReportsPage();
},
}
Результат: модуль загружается только при переходе на
/reports, что снижает нагрузку на начальную загрузку и
пересчёты других маршрутов.
actionЕсли action использует повторяющиеся вычисления внутри
маршрута, их стоит мемоизировать:
import memoizee from 'memoizee';
const computeHeavy = memoizee(async (param) => {
// тяжёлое вычисление
return await fetch(`/api/data/${param}`).then(res => res.json());
});
const routes = [
{
path: '/data/:id',
async action({ params }) {
return computeHeavy(params.id);
},
},
];
Вывод: при повторном вызове с тем же id
операция выполняется один раз, что минимизирует пересчёты.
Universal Router передаёт параметры в action только
после совпадения пути. Чтобы уменьшить лишние вычисления при сложных
регулярных путях, можно использовать ленивое создание
параметров:
{
path: '/products/:category/:id',
action: ({ params }) => {
const id = parseInt(params.id, 10); // вычисление только при необходимости
return `Категория ${params.category}, продукт ${id}`;
},
}
Преимущество: вычисления выполняются только при точном совпадении пути.
Universal Router выполняет последовательное сравнение маршрутов. Для больших приложений стоит:
const routes = [
{ path: '/', action: () => 'Home' }, // частый маршрут наверху
{ path: '/admin/:page', action: () => 'Admin' }, // менее частый
];
Даже при оптимизированных маршрутах лишние вызовы
resolve создают пересчёты. Практика:
resolve.let currentPath = null;
async function navigate(path) {
if (path === currentPath) return;
currentPath = path;
const result = await router.resolve(path);
render(result);
}
Эти техники в комплексе позволяют строить эффективные приложения с Universal Router, где пересчёты минимизированы, навигация быстрая, а ресурсы расходуются только по необходимости.
Ключевые принципы: кэширование, ленивость, мемоизация, структуризация маршрутов и контроль повторных вызовов.