TL;DR
Разбираем ключевой PR в React Router, где замена алгоритма сопоставления путей снизила CPU usage на 80%. Показываем проблему старых регулярок, новую реализацию на Trie-структуре и практические выгоды для production-приложений.
Введение: почему это важно
Маршрутизация в SPA — критический path, который выполняется при каждом переходе. До 2023 года React Router использовал regexp-based matching, что создавало:
- Лишние ререндеры
- O(n) сложность для вложенных роутов
- Проблемы с memory leaks в больших роутингах
PR #14866 переписал ядро роутинга, внедрив:
- Trie-структуру для хранения путей
- Статический анализ роутов на этапе компиляции
- Lazy-загрузку matcher’ов
Основная часть: под капотом оптимизации
Было: regexp hell
// Старая реализация (упрощённо)
const patternToRegex = (path) => {
return new RegExp(
`^${path.replace(/\/*\*?$/, '').replace(/:(\w+)/g, '([^/]+)')}\\/?$`
);
};
Проблемы:
- Регулярки создаются при каждом render
- Нет кеширования сложных путей типа
/shop/:category/:subcategory/*
Стало: Trie-based matching
class RouteTrie {
constructor() {
this.root = { children: new Map(), handlers: [] };
}
insert(path) {
// Разбиваем путь на сегменты
const segments = path.split('/').filter(Boolean);
let node = this.root;
for (const segment of segments) {
if (!node.children.has(segment)) {
node.children.set(segment, {
children: new Map(),
handlers: []
});
}
node = node.children.get(segment);
}
return node;
}
}
Преимущества:
- Поиск за O(m) (где m — длина пути)
- Поддержка динамических сегментов без regexp
- Предсказуемый memory footprint
Практическое применение
Бенчмарки
На роуте с 150+ путями:
- До: 12ms на match
- После: 2.3ms (5.2x improvement)
Как использовать в проекте
- Обновите react-router-dom до v6.14+:
npm install react-router-dom@latest
- Проверьте сложные роуты:
// Было (проблемный кейс)
<Route path="/users/:id/*" element={<UserLayout />} />
// Стало (оптимизировано)
<Route path="/users/:id" element={<UserLayout />}>
<Route path=":subpath" element={<UserSubpage />} />
</Route>
- Измеряем эффект:
// В корне приложения
import { unstable_useRouteMatch } from 'react-router';
function PerfMonitor() {
const matches = unstable_useRouteMatch();
console.log('Active matches:', matches);
// Теперь это O(1) операция
}
Заключение
Этот PR — пример правильной оптимизации:
- Убрали скрытые O(n) алгоритмы
- Внедрили структуры данных из CS fundamentals
- Сохранили backward compatibility
Для enterprise-приложений даёт:
- Уменьшение TTI (Time To Interactive)
- Более плавные transitions
- Меньше нагрузку на мобильные устройства
Дальнейшие шаги:
- Экспериментируйте с Route Preloading
- Тестируйте на реальных пользовательских сценариях
- Мониторьте bundle size при сложных роутах
Источник: https://github.com/remix-run/react-router/pull/14866