Data Router API: альтернативний синтаксис через createBrowserRouter
Data Router API: альтернативний синтаксис через createBrowserRouter
🎯 Мета лекції
- Опанувати Data Router API як альтернативу JSX-синтаксису.
- Навчитися визначати маршрути через масив об'єктів.
- Зрозуміти переваги централізованої конфігурації маршрутів.
- Побачити еквіваленти всіх патернів у об'єктному синтаксисі.
🔑 Ключові терміни
- createBrowserRouter: функція для створення router-інстансу з масиву об'єктів маршрутів.
- RouterProvider: компонент для рендерингу router-інстансу замість
<BrowserRouter>. - Route Object: об'єкт конфігурації маршруту з полями
path,element,children, тощо. - Data Router API: підхід до визначення маршрутів, що підтримує loaders, actions та errorElement.
Два способи визначення маршрутів
React Router v7 підтримує два синтаксиси для визначення маршрутів:
1. JSX-синтаксис (Declarative Routing)
import { BrowserRouter, Routes, Route } from "react-router";
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
</Routes>
</BrowserRouter>
Переваги:
- Візуально наочний
- Природний для React-розробників
- Компоненти можна розподіляти по файлах
2. Data Router API (Object-based Routing)
import { createBrowserRouter, RouterProvider } from "react-router";
const router = createBrowserRouter([
{ path: "/", element: <HomePage /> },
{ path: "/about", element: <AboutPage /> },
]);
<RouterProvider router={router} />
Переваги:
- Централізована конфігурація
- Підтримка data loaders та actions
- Typed Routes через TypeScript
- Легше для програмної маніпуляції маршрутами
- Простий SPA без data fetching → обидва підходи рівноцінні
- Складний застосунок із loaders/actions → Data Router API краще
- Поступова міграція з v6 → JSX-синтаксис (сумісний з v6)
Базові маршрути
JSX-синтаксис
import { BrowserRouter, Routes, Route, Link } from "react-router";
function Navigation() {
return (
<nav>
<Link to="/">Головна</Link>
<Link to="/about">Про нас</Link>
<Link to="/contact">Контакти</Link>
</nav>
);
}
function App() {
return (
<BrowserRouter>
<Navigation />
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/contact" element={<ContactPage />} />
</Routes>
</BrowserRouter>
);
}
Data Router API (еквівалент)
import { createBrowserRouter, RouterProvider, Link, Outlet } from "react-router";
// Layout для навігації
function RootLayout() {
return (
<>
<nav>
<Link to="/">Головна</Link>
<Link to="/about">Про нас</Link>
<Link to="/contact">Контакти</Link>
</nav>
<Outlet /> {/* Тут рендериться активний маршрут */}
</>
);
}
const router = createBrowserRouter([
{
path: "/",
element: <RootLayout />,
children: [
{ index: true, element: <HomePage /> },
{ path: "about", element: <AboutPage /> },
{ path: "contact", element: <ContactPage /> },
],
},
]);
function App() {
return <RouterProvider router={router} />;
}
Вкладені маршрути та Outlet
JSX-синтаксис
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardHome />} />
<Route path="profile" element={<ProfilePage />} />
<Route path="settings" element={<SettingsPage />} />
</Route>
</Routes>
Data Router API (еквівалент)
const router = createBrowserRouter([
{
path: "/dashboard",
element: <DashboardLayout />,
children: [
{ index: true, element: <DashboardHome /> },
{ path: "profile", element: <ProfilePage /> },
{ path: "settings", element: <SettingsPage /> },
],
},
]);
Створені URL:
/dashboard→ DashboardLayout + DashboardHome/dashboard/profile→ DashboardLayout + ProfilePage/dashboard/settings→ DashboardLayout + SettingsPage
Layout Routes (без path)
JSX-синтаксис
<Routes>
<Route element={<PublicLayout />}>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
</Route>
<Route element={<PrivateLayout />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
</Route>
</Routes>
Data Router API (еквівалент)
const router = createBrowserRouter([
{
element: <PublicLayout />,
children: [
{ path: "/", element: <HomePage /> },
{ path: "/about", element: <AboutPage /> },
],
},
{
element: <PrivateLayout />,
children: [
{ path: "/dashboard", element: <Dashboard /> },
{ path: "/profile", element: <Profile /> },
],
},
]);
Динамічні параметри
JSX-синтаксис
<Routes>
<Route path="/users/:userId" element={<UserProfile />} />
<Route path="/posts/:postId" element={<PostDetails />} />
<Route path="/categories/:categoryId/products/:productId" element={<Product />} />
</Routes>
Data Router API (еквівалент)
const router = createBrowserRouter([
{ path: "/users/:userId", element: <UserProfile /> },
{ path: "/posts/:postId", element: <PostDetails />} ,
{ path: "/categories/:categoryId/products/:productId", element: <Product /> },
]);
Витягування параметрів ідентичне в обох підходах через useParams():
import { useParams } from "react-router";
function UserProfile() {
const { userId } = useParams();
return <div>Користувач: {userId}</div>;
}
Необов'язкові сегменти та Wildcard
JSX-синтаксис
<Routes>
<Route path="/:lang?/categories" element={<Categories />} />
<Route path="/docs/*" element={<Documentation />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
Data Router API (еквівалент)
const router = createBrowserRouter([
{ path: "/:lang?/categories", element: <Categories /> },
{ path: "/docs/*", element: <Documentation /> },
{ path: "*", element: <NotFoundPage /> },
]);
Обробка помилок: ErrorBoundary
JSX-синтаксис + react-error-boundary
import { ErrorBoundary } from "react-error-boundary";
<BrowserRouter>
<ErrorBoundary FallbackComponent={ErrorFallback}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/broken" element={<BrokenComponent />} />
</Routes>
</ErrorBoundary>
</BrowserRouter>
Data Router API (вбудована підтримка)
function ErrorFallback() {
return (
<div>
<h2>Щось пішло не так</h2>
<button onClick={() => window.location.reload()}>
Перезавантажити
</button>
</div>
);
}
const router = createBrowserRouter([
{
path: "/",
element: <RootLayout />,
errorElement: <ErrorFallback />, // Обробка помилок на рівні маршруту!
children: [
{ index: true, element: <HomePage /> },
{ path: "broken", element: <BrokenComponent /> },
],
},
]);
errorElement працює на рівні конкретного маршруту, що дозволяє мати різні fallback-компоненти для різних секцій застосунку без додаткових бібліотек.Глибока вкладеність: три рівні
JSX-синтаксис
<Routes>
<Route path="/projects" element={<ProjectsLayout />}>
<Route index element={<ProjectsList />} />
<Route path=":projectId" element={<ProjectLayout />}>
<Route index element={<ProjectOverview />} />
<Route path="tasks" element={<TasksList />} />
<Route path="team" element={<TeamMembers />} />
</Route>
</Route>
</Routes>
Data Router API (еквівалент)
const router = createBrowserRouter([
{
path: "/projects",
element: <ProjectsLayout />,
children: [
{ index: true, element: <ProjectsList /> },
{
path: ":projectId",
element: <ProjectLayout />,
children: [
{ index: true, element: <ProjectOverview /> },
{ path: "tasks", element: <TasksList /> },
{ path: "team", element: <TeamMembers /> },
],
},
],
},
]);
Створені URL:
/projects→ ProjectsList/projects/abc-123→ ProjectOverview/projects/abc-123/tasks→ TasksList/projects/abc-123/team→ TeamMembers
Винесення конфігурації у окремий файл
Для великих застосунків рекомендується структурувати маршрути:
// src/router/index.tsx
import { createBrowserRouter } from "react-router";
import { publicRoutes } from "./publicRoutes";
import { dashboardRoutes } from "./dashboardRoutes";
import { adminRoutes } from "./adminRoutes";
export const router = createBrowserRouter([
...publicRoutes,
...dashboardRoutes,
...adminRoutes,
]);
// src/router/publicRoutes.tsx
import { RouteObject } from "react-router";
import RootLayout from "@/layouts/RootLayout";
import HomePage from "@/pages/HomePage";
import AboutPage from "@/pages/AboutPage";
export const publicRoutes: RouteObject[] = [
{
path: "/",
element: <RootLayout />,
children: [
{ index: true, element: <HomePage /> },
{ path: "about", element: <AboutPage /> },
],
},
];
// src/router/dashboardRoutes.tsx
import { RouteObject } from "react-router";
import DashboardLayout from "@/layouts/DashboardLayout";
import DashboardHome from "@/pages/Dashboard/DashboardHome";
import ProfilePage from "@/pages/Dashboard/ProfilePage";
export const dashboardRoutes: RouteObject[] = [
{
path: "/dashboard",
element: <DashboardLayout />,
children: [
{ index: true, element: <DashboardHome /> },
{ path: "profile", element: <ProfilePage /> },
],
},
];
RouteObject забезпечує типобезпеку TypeScript для об'єктів маршрутів. Використовуйте його для всіх експортованих конфігурацій маршрутів.Типізація маршрутів через TypeScript
Data Router API чудово працює з TypeScript:
import { createBrowserRouter, RouteObject } from "react-router";
// Типізація параметрів маршруту
type UserProfileParams = {
userId: string;
};
// Типізація об'єкта маршруту
const userRoutes: RouteObject[] = [
{
path: "/users/:userId",
element: <UserProfile />,
},
];
// Використання у компоненті з типізацією
function UserProfile() {
const { userId } = useParams<UserProfileParams>();
if (!userId) {
return <div>Помилка: ID не вказано</div>;
}
return <div>Профіль користувача {userId}</div>;
}
Порівняльна таблиця
| Функція | JSX-синтаксис | Data Router API |
|---|---|---|
| Базовий маршрут | <Route path="/" element={<Home />} /> | { path: "/", element: <Home /> } |
| Вкладені маршрути | <Route> всередині <Route> | children: [...] у об'єкті |
| Index маршрут | <Route index element={...} /> | { index: true, element: ... } |
| Layout Route | <Route element={<Layout />}> | { element: <Layout />, children: [...] } |
| Динамічні параметри | path="/users/:id" | path: "/users/:id" |
| Wildcard | path="*" | path: "*" |
| ErrorBoundary | Зовнішня бібліотека | errorElement: <Fallback /> (вбудовано) |
| Data Loaders | Не підтримується | loader: async () => {...} |
| Form Actions | Не підтримується | action: async () => {...} |
Коли використовувати кожен підхід?
Використовуйте JSX-синтаксис (<Routes>), якщо:
- Мігруєте з React Router v6 і хочете мінімальних змін
- Проєкт простий, без data fetching на рівні маршрутів
- Команда звикла до декларативного JSX-стилю
- Маршрути розподілені по різних файлах/компонентах
Використовуйте Data Router API (createBrowserRouter), якщо:
- Потрібні data loaders та actions
- Хочете централізовану конфігурацію маршрутів
- Потрібна вбудована обробка помилок через
errorElement - Плануєте програмно маніпулювати маршрутами
- Потрібна максимальна типобезпека TypeScript
Підсумок
✅ Що ми опанували
- Два способи визначення маршрутів: JSX та Data Router API.
- Створення базових та вкладених маршрутів через об'єкти.
- Переваги централізованої конфігурації через
createBrowserRouter. - Вбудовану обробку помилок через
errorElement. - Структурування маршрутів для великих проєктів.
- Типізацію маршрутів через TypeScript.
🎯 Ключові переваги Data Router API
- Централізація: всі маршрути в одному місці.
- Типобезпека: повна підтримка TypeScript.
- Розширюваність: loaders, actions, errorElement.
- Програмна маніпуляція: легко генерувати маршрути динамічно.
- Майбутнє: всі нові функції додаватимуться саме сюди.
Обробка помилок та 404: ErrorBoundary і fallback-маршрути
Створення сторінок 404 для неіснуючих маршрутів, обробка помилок рендерингу через ErrorBoundary, catch-all маршрути через wildcard, типізовані компоненти помилок, користувацький UX для помилкових станів.
Axios: HTTP-клієнт із розширеними можливостями
Опанування Axios як альтернативи fetch API: типізовані запити, interceptors, обробка помилок, скасування та кешування запитів у React із TypeScript