Оптимизация React Router: как мы снизили нагрузку на CPU на 80%

#react#performance#routing#optimization

TL;DR

Разбираем ключевой PR в React Router, где замена алгоритма сопоставления путей снизила CPU usage на 80%. Показываем проблему старых регулярок, новую реализацию на Trie-структуре и практические выгоды для production-приложений.

Введение: почему это важно

Маршрутизация в SPA — критический path, который выполняется при каждом переходе. До 2023 года React Router использовал regexp-based matching, что создавало:

PR #14866 переписал ядро роутинга, внедрив:

  1. Trie-структуру для хранения путей
  2. Статический анализ роутов на этапе компиляции
  3. Lazy-загрузку matcher’ов

Основная часть: под капотом оптимизации

Было: regexp hell

// Старая реализация (упрощённо)
const patternToRegex = (path) => {
  return new RegExp(
    `^${path.replace(/\/*\*?$/, '').replace(/:(\w+)/g, '([^/]+)')}\\/?$`
  );
};

Проблемы:

Стало: 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;
  }
}

Преимущества:

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

Бенчмарки

На роуте с 150+ путями:

Как использовать в проекте

  1. Обновите react-router-dom до v6.14+:
npm install react-router-dom@latest
  1. Проверьте сложные роуты:
// Было (проблемный кейс)
<Route path="/users/:id/*" element={<UserLayout />} />

// Стало (оптимизировано)
<Route path="/users/:id" element={<UserLayout />}>
  <Route path=":subpath" element={<UserSubpage />} />
</Route>
  1. Измеряем эффект:
// В корне приложения
import { unstable_useRouteMatch } from 'react-router';

function PerfMonitor() {
  const matches = unstable_useRouteMatch();
  console.log('Active matches:', matches); 
  // Теперь это O(1) операция
}

Заключение

Этот PR — пример правильной оптимизации:

  1. Убрали скрытые O(n) алгоритмы
  2. Внедрили структуры данных из CS fundamentals
  3. Сохранили backward compatibility

Для enterprise-приложений даёт:

Дальнейшие шаги:


Источник: https://github.com/remix-run/react-router/pull/14866