Universal Router в связке с TypeScript позволяет строго типизировать
маршруты, их параметры и возвращаемые значения. Основная идея —
определить интерфейсы для параметров каждого маршрута и использовать их
при описании маршрутов и вызове router.resolve.
import UniversalRouter, { Route } from 'universal-router';
interface RouteParams {
id?: string;
category?: string;
}
const routes: Route<RouteParams>[] = [
{
path: '/',
action: () => 'Главная страница',
},
{
path: '/product/:id',
action: ({ params }) => `Продукт с ID: ${params.id}`,
},
{
path: '/category/:category',
action: ({ params }) => `Категория: ${params.category}`,
},
];
const router = new UniversalRouter<RouteParams>(routes);
(async () => {
const result = await router.resolve({ pathname: '/product/42' });
console.log(result); // Продукт с ID: 42
})();
Ключевое здесь — параметризация
Route<RouteParams>, которая гарантирует наличие
ожидаемых параметров и их типизацию в каждом маршруте. Если попытаться
обратиться к несуществующему параметру, TypeScript выдаст ошибку.
TypeScript позволяет задать не только типы параметров, но и тип возвращаемого значения маршрута. Это особенно полезно для маршрутов, которые возвращают объекты, а не строки.
interface User {
id: string;
name: string;
}
interface UserRouteParams {
id: string;
}
const userRoutes: Route<UserRouteParams, User>[] = [
{
path: '/user/:id',
async action({ params }) {
const user: User = await fetchUser(params.id);
return user;
},
},
];
const userRouter = new UniversalRouter<UserRouteParams, User>(userRoutes);
async function fetchUser(id: string): Promise<User> {
return { id, name: 'Иван' };
}
(async () => {
const user = await userRouter.resolve({ pathname: '/user/123' });
console.log(user.name); // Иван
})();
Использование Generics делает работу с данными безопасной и предотвращает типовые ошибки при обращении к свойствам объекта, возвращаемого маршрутом.
Universal Router позволяет передавать контекст, который доступен в каждом маршруте. TypeScript позволяет типизировать этот контекст для строгой проверки.
interface AppContext {
isAuthenticated: boolean;
userRole: 'admin' | 'user';
}
interface ContextRouteParams {
section?: string;
}
const contextRoutes: Route<ContextRouteParams, string, AppContext>[] = [
{
path: '/admin',
action: ({ context }) => {
if (!context.isAuthenticated || context.userRole !== 'admin') {
return 'Доступ запрещён';
}
return 'Панель администратора';
},
},
];
const contextRouter = new UniversalRouter<ContextRouteParams, string, AppContext>(
contextRoutes
);
(async () => {
const result = await contextRouter.resolve({ pathname: '/admin', context: { isAuthenticated: true, userRole: 'admin' } });
console.log(result); // Панель администратора
})();
Контекст позволяет расширять функциональность маршрутов и строго контролировать доступ, сохраняя при этом статическую проверку типов.
В Universal Router маршруты могут быть вложенными. TypeScript позволяет сохранять строгую типизацию при использовании вложенных маршрутов.
interface NestedParams {
section: string;
page?: string;
}
const nestedRoutes: Route<NestedParams>[] = [
{
path: '/docs',
children: [
{
path: ':section',
action: ({ params }) => `Документация: ${params.section}`,
children: [
{
path: ':page',
action: ({ params }) => `Страница: ${params.page} в разделе ${params.section}`,
},
],
},
],
},
];
const nestedRouter = new UniversalRouter<NestedParams>(nestedRoutes);
(async () => {
const result = await nestedRouter.resolve({ pathname: '/docs/javascript/intro' });
console.log(result); // Страница: intro в разделе javascript
})();
Вложенные маршруты с типизацией гарантируют корректность параметров на каждом уровне маршрута, предотвращая ошибки доступа к неопределённым параметрам.
Universal Router позволяет маршрутам быть асинхронными. TypeScript
позволяет точно указать, что возвращаемое значение является
Promise<T> и какие данные оно содержит.
interface AsyncParams {
id: string;
}
interface AsyncData {
title: string;
content: string;
}
const asyncRoutes: Route<AsyncParams, Promise<AsyncData>>[] = [
{
path: '/article/:id',
async action({ params }) {
const data: AsyncData = await fetchArticle(params.id);
return data;
},
},
];
async function fetchArticle(id: string): Promise<AsyncData> {
return { title: `Статья ${id}`, content: 'Содержимое статьи' };
}
const asyncRouter = new UniversalRouter<AsyncParams, Promise<AsyncData>>(asyncRoutes);
(async () => {
const article = await asyncRouter.resolve({ pathname: '/article/10' });
console.log(article.title); // Статья 10
})();
Строгая типизация асинхронных маршрутов предотвращает ошибки при работе с промисами и позволяет точно определять структуру данных, получаемых в результате.
При работе с Universal Router часто создаются вспомогательные функции для генерации путей и параметров. Использование TypeScript позволяет типизировать эти функции для согласованности с маршрутами.
interface ProfileParams {
userId: string;
}
const profileRoutes: Route<ProfileParams>[] = [
{ path: '/profile/:userId', action: () => 'Профиль пользователя' },
];
function buildProfilePath(params: ProfileParams): string {
return `/profile/${params.userId}`;
}
const profileRouter = new UniversalRouter<ProfileParams>(profileRoutes);
const path = buildProfilePath({ userId: '42' });
console.log(path); // /profile/42
Типизация функции генерации путей гарантирует, что путь будет соответствовать определённому маршруту и передаваемые параметры будут корректны.
Universal Router хорошо интегрируется с React, а TypeScript позволяет строго типизировать возвращаемые компоненты и контекст.
import React from 'react';
import UniversalRouter, { Route } from 'universal-router';
interface ReactParams {
page: string;
}
const routes: Route<ReactParams, React.ReactNode>[] = [
{
path: '/home',
action: () => <h1>Главная страница</h1>,
},
{
path: '/about',
action: () => <h1>О нас</h1>,
},
];
const router = new UniversalRouter<ReactParams, React.ReactNode>(routes);
(async () => {
const component = await router.resolve({ pathname: '/about' });
// component можно напрямую рендерить в React
})();
Типизация компонентов предотвращает ошибки при интеграции маршрутизатора с системой рендеринга и позволяет использовать преимущества автодополнения в редакторах кода.