Вкладені Маршрути та Макети
Вкладені Маршрути та Макети
Це "кілер-фіча" React Router. Якщо ви зрозумієте цей розділ, ви зрозумієте 80% архітектури складних React-додатків.
У більшості роутерів (в інших мовах чи фреймворках) маршрут — це просто спосіб зіставити URL з екраном. В React Router маршрут — це спосіб зіставити сегмент URL з сегментом UI.
1. Ментальна модель: UI вкладений в URL
Подивіться на типовий веб-інтерфейс, наприклад, панель керування:
/dashboard/settings/profile
+----------------------------------------------------+
| Global Navbar (Лого, Меню) |
| +------------------------------------------------+ |
| | Dashboard Sidebar (Меню дашборда) | |
| | +--------------------------------------------+ | |
| | | Settings Tabs (Профіль, Безпека) | | |
| | | +----------------------------------------+ | | |
| | | | | | | |
| | | | Profile Form (Власне контент) | | | |
| | | | | | | |
| | | +----------------------------------------+ | | |
| | +--------------------------------------------+ | |
| +------------------------------------------------+ |
+----------------------------------------------------+
/відповідає заGlobal Navbar/dashboardдодаєSidebar/settingsдодаєTabs/profileпоказуєForm
Коли URL змінюється з /dashboard/settings/profile на /dashboard/settings/security:
- Navbar залишається.
- Sidebar залишається.
- Tabs залишаються.
- Змінюється тільки форма всередині.
React Router дозволяє відобразити цю вкладеність прямо в конфігурації маршрутів.
2. Компонент <Outlet>
Щоб реалізувати цю вкладеність, батьківський маршрут повинен знати, куди саме вставити дочірній компонент. Для цього використовується компонент <Outlet>.
Думайте про <Outlet> як про props.children, але контрольований роутером.
3. Створення Root Layout (Кореневий Макет)
У попередніх уроках ми мали проблему: навігаційне меню довелося б копіювати на кожну сторінку. Давайте виправимо це, створивши спільний макет для всього сайту.
Крок 1: Створення компонента Layout
import { Outlet, NavLink } from 'react-router-dom'
export default function RootLayout() {
return (
<div className="app-container">
{/* Ця частина буде на ВСІХ сторінках */}
<header>
<nav>
<NavLink to="/">Головна</NavLink>
<NavLink to="/about">Про нас</NavLink>
</nav>
</header>
{/* Сюди роутер підставить контент поточної сторінки */}
<main>
<Outlet />
</main>
<footer>
<p>© 2026 Мій Сайт</p>
</footer>
</div>
)
}
Крок 2: Оновлення конфігурації роутера
Тепер ми використаємо властивість children в об'єкті маршруту.
import RootLayout from './layouts/RootLayout'
// ... інші імпорти
const router = createBrowserRouter([
{
path: '/',
element: <RootLayout />, // Головний батько
errorElement: <ErrorPage />,
children: [
{
// Це Index Route (про нього нижче)
index: true,
element: <HomePage />,
},
{
path: 'about', // Зверніть увагу: без слеша "/" на початку!
element: <AboutPage />,
},
],
},
])
Що змінилося:
- Коли URL
/, рендериться<RootLayout>. Всередині його<Outlet />рендериться<HomePage>. - Коли URL
/about, рендериться<RootLayout>. Всередині його<Outlet />рендериться<AboutPage>.
4. Index Routes (Індексні маршрути)
Ви могли помітити index: true замість path: "" або path: "/".
Індексний маршрут — це "маршрут за замовчуванням" для батьківського шляху.
Коли URL точно збігається з шляхом батька (у нашому випадку /), батько показує element, але в <Outlet> було б порожньо, якби не індексний маршрут.
Уявіть це як index.html у папці.
children: [
{
index: true, // URL: /
element: <Home />,
},
{
path: 'products', // URL: /products
element: <ProductsLayout />,
children: [
{ index: true, element: <AllProductsList /> }, // URL: /products
{ path: 'new', element: <NewProductForm /> }, // URL: /products/new
],
},
]
5. Відносні шляхи (Relative Paths)
Зверніть увагу, що у дочірніх маршрутах ми пишемо path: "about", а не path: "/about".
- Абсолютний шлях (
/about): Завжди рахується від кореня домену. - Відносний шлях (
about): Рахується від шляху батька.
Якщо батько має шлях /dashboard, а дитина settings, то кінцевий URL буде /dashboard/settings. Це дозволяє легко змінювати URL батька, не ламаючи всі дочірні маршрути.
6. Приклад: Глибока вкладеність
Давайте реалізуємо структуру з початку уроку.
const router = createBrowserRouter([
{
path: '/',
element: <RootLayout />, // Має <Outlet>
children: [
{
path: 'dashboard',
element: <DashboardLayout />, // Має свій <Outlet>!
children: [
{
index: true,
element: <DashboardHome />, // Статистика
},
{
path: 'settings',
element: <SettingsLayout />, // І тут теж <Outlet>!
children: [
{ index: true, element: <ProfileSettings /> },
{ path: 'security', element: <SecuritySettings /> },
],
},
],
},
],
},
])
Як це рендериться при URL /dashboard/settings/security:
<RootLayout>
<DashboardLayout>
<SettingsLayout>
<SecuritySettings />
</SettingsLayout>
</DashboardLayout>
</RootLayout>
Кожен макет відповідає тільки за свою частину UI і делегує решту через <Outlet />. Це робить код неймовірно модульним.
Резюме
- Вкладені маршрути дозволяють будувати UI, який складається з вкладених шарів.
<Outlet>— це місце, куди батьківський маршрут виводить дочірній.- Index Route — це те, що показується в
<Outlet>, коли URL точно збігається з батьківським. - Відносні шляхи спрощують рефакторинг і читання конфігурації.
Ми побудували статичну структуру. Але що робити, якщо частина URL — це динамічний ID (наприклад, /product/123)? Про це — у наступному розділі.
Динамічна Навігація
Тепер, коли у нас є налаштований роутер, нам потрібно якось переміщатися між сторінками. У світі HTML ми звикли використовувати тег <a>. Але в SPA це "заборонений прийом".
Динамічні Маршрути та Параметри
До цього моменту ми працювали з фіксованими URL: /about, /contact. Але реальні додатки працюють з динамічними даними: користувачі (/user/123), товари (/products/iphone-15) або замовлення (/order/abc-999).