React Router долгое время являлся стандартом де-факто для клиентской маршрутизации в React-приложениях. Однако с появлением более универсальных решений, таких как Universal Router, возникла необходимость пересмотра архитектуры маршрутизации.
Ключевые ограничения React Router:
Universal Router предлагает:
<Route>,
<Switch>)useNavigate,
useParams)<Routes>
<Route path="/" element={<Home />} />
<Route path="/users" element={<Users />} />
<Route path="/users/:id" element={<UserProfile />} />
</Routes>
const routes = [
{
path: '/',
action: () => renderHome()
},
{
path: '/users',
action: () => renderUsers()
},
{
path: '/users/:id',
action: ({ params }) => renderUserProfile(params.id)
}
];
Ключевое отличие: вместо компонентов используются
функции action, которые полностью контролируют
поведение.
Роутер инициализируется через компонент:
<BrowserRouter>
<App />
</BrowserRouter>
import UniversalRouter from 'universal-router';
const router = new UniversalRouter(routes);
Дополнительно можно передать контекст:
const router = new UniversalRouter(routes, {
context: { user: currentUser }
});
const navigate = useNavigate();
navigate('/users/1');
router.resolve('/users/1');
Асинхронный характер:
await router.resolve('/users/1');
Это позволяет:
const { id } = useParams();
{
path: '/users/:id',
action: ({ params }) => {
return renderUser(params.id);
}
}
Параметры передаются напрямую в action.
Одно из ключевых преимуществ Universal Router — встроенная поддержка асинхронности.
{
path: '/users/:id',
async action({ params }) {
const user = await fetchUser(params.id);
return renderUser(user);
}
}
В React Router асинхронность обычно выносится в компоненты или loaders (в новых версиях), но контроль менее централизован.
<Route path="/users" element={<Users />}>
<Route path=":id" element={<UserProfile />} />
</Route>
{
path: '/users',
children: [
{
path: '/',
action: () => renderUsers()
},
{
path: '/:id',
action: ({ params }) => renderUser(params.id)
}
]
}
Вложенность реализуется через массив children.
Universal Router позволяет передавать контекст в каждый маршрут:
const router = new UniversalRouter(routes, {
context: {
user: currentUser,
api: apiClient
}
});
Использование:
{
path: '/dashboard',
action: ({ context }) => {
if (!context.user) {
return redirect('/login');
}
return renderDashboard();
}
}
<Navigate to="/login" />
import { redirect } from 'universal-router';
{
path: '/private',
action: ({ context }) => {
if (!context.user) {
return redirect('/login');
}
}
}
Редирект происходит на уровне логики маршрута.
Universal Router не занимается рендерингом. Интеграция выполняется вручную:
async function navigate(path) {
const result = await router.resolve(path);
ReactDOM.render(result, document.getElementById('root'));
}
Маршрут возвращает React-элемент:
{
path: '/',
action: () => <Home />
}
Universal Router не управляет history напрямую. Обычно используется
history пакет:
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
history.listen(({ location }) => {
router.resolve(location.pathname);
});
Навигация:
history.push('/users');
const router = new UniversalRouter(routes, {
errorHandler(error) {
console.error(error);
return renderErrorPage(error);
}
});
В отличие от React Router, ошибки обрабатываются централизованно.
{
path: '/admin',
async action() {
const module = await import('./AdminPage');
return module.default();
}
}
Позволяет:
Universal Router поддерживает цепочки обработки:
const routes = [
{
path: '',
action: async (context, params, next) => {
console.log('before');
const result = await next();
console.log('after');
return result;
},
children: [
{
path: '/users',
action: () => renderUsers()
}
]
}
];
Это аналог middleware:
Universal Router изначально проектировался для SSR:
app.get('*', async (req, res) => {
const html = await router.resolve(req.path);
res.send(renderFullPage(html));
});
Преимущества:
Было:
<Route path="/users" element={<Users />} />
Станет:
{
path: '/users',
action: () => <Users />
}
Перенос:
из компонентов в action.
Было:
navigate('/users');
Станет:
router.resolve('/users');
или через history.
Удаляются:
<Routes><Route><Link> (заменяется на собственную
реализацию)Добавляется управление URL вручную.
Двойной рендеринг
Причина: повторный вызов resolve. Решение:
централизовать обработку переходов.
Потеря состояния
React Router автоматически сохраняет состояние компонентов. В Universal Router это нужно учитывать вручную.
Отсутствие хуков
Hooks (useParams, useLocation) отсутствуют.
Аналог реализуется через контекст или props.