Вложенные layout

React Router предоставляет возможность организовывать маршруты приложения так, чтобы одни компоненты рендерились внутри других. Это называется вложенными layout и является ключевым инструментом для построения сложных интерфейсов с повторяющимися структурами, например, боковыми панелями, навигацией или хедером.


Основы вложенных маршрутов

В React Router вложенные маршруты создаются с помощью компонента <Outlet>. Этот компонент указывает, где внутри родительского layout будут рендериться дочерние маршруты. Простейший пример:

import { BrowserRouter, Routes, Route, Outlet } from 'react-router-dom';

function DashboardLayout() {
  return (
    <div>
      <header>Панель навигации</header>
      <main>
        <Outlet />
      </main>
    </div>
  );
}

function Overview() {
  return <h2>Обзор</h2>;
}

function Reports() {
  return <h2>Отчёты</h2>;
}

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/dashboard" element={<DashboardLayout />}>
          <Route index element={<Overview />} />
          <Route path="reports" element={<Reports />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

Здесь DashboardLayout является родительским layout, а <Outlet /> рендерит дочерние маршруты /dashboard и /dashboard/reports. Использование index задаёт маршрут по умолчанию внутри родителя.


Многоуровневая вложенность

React Router поддерживает неограниченное количество уровней вложенности. Можно создавать вложенные layout внутри других layout. Пример:

function AdminLayout() {
  return (
    <div>
      <nav>Админ панель</nav>
      <section>
        <Outlet />
      </section>
    </div>
  );
}

function UsersLayout() {
  return (
    <div>
      <h3>Пользователи</h3>
      <Outlet />
    </div>
  );
}

function UserList() {
  return <p>Список пользователей</p>;
}

function UserDetail() {
  return <p>Детали пользователя</p>;
}

<Routes>
  <Route path="/admin" element={<AdminLayout />}>
    <Route path="users" element={<UsersLayout />}>
      <Route index element={<UserList />} />
      <Route path=":id" element={<UserDetail />} />
    </Route>
  </Route>
</Routes>

В этом примере структура /admin/users/:id рендерится внутри UsersLayout, который сам находится в AdminLayout. Таким образом можно создавать иерархические интерфейсы, не дублируя код навигации или хедера.


Использование <Outlet> с контекстом

Для передачи данных из родительского layout в дочерние маршруты удобно использовать React Context. Пример:

import { createContext, useContext } from 'react';

const UserContext = createContext();

function UsersLayout() {
  const users = ['Alice', 'Bob', 'Charlie'];
  
  return (
    <UserContext.Provider value={users}>
      <h2>Пользователи</h2>
      <Outlet />
    </UserContext.Provider>
  );
}

function UserList() {
  const users = useContext(UserContext);
  return (
    <ul>
      {users.map(user => <li key={user}>{user}</li>)}
    </ul>
  );
}

Дочерние маршруты автоматически получают доступ к данным через контекст, что делает layout мощным инструментом для централизованного управления состоянием на уровне маршрутов.


Вложенные layout с навигацией и динамическими маршрутами

React Router позволяет комбинировать динамические маршруты с layout. Например:

function BlogLayout() {
  return (
    <div>
      <header>Блог</header>
      <Outlet />
    </div>
  );
}

function BlogList() {
  return <p>Список постов</p>;
}

function BlogPost() {
  const { postId } = useParams();
  return <p>Пост с ID: {postId}</p>;
}

<Routes>
  <Route path="/blog" element={<BlogLayout />}>
    <Route index element={<BlogList />} />
    <Route path=":postId" element={<BlogPost />} />
  </Route>
</Routes>

Здесь :postId динамически рендерится внутри родительского layout BlogLayout. Такой подход позволяет разделять общий интерфейс и контентные блоки без повторного кода.


Ключевые рекомендации по вложенным layout

  • Использовать <Outlet> только один раз в родительском layout для предотвращения ошибок рендеринга.
  • Комбинировать index- и динамические маршруты для удобной навигации по умолчанию и специфическим страницам.
  • Разделять layout по смысловым блокам, например, AdminLayout, UsersLayout, BlogLayout, чтобы повторное использование компонентов было максимально эффективным.
  • Передавать данные через контекст или props, чтобы дочерние маршруты имели доступ к состоянию родителя без необходимости поднятия состояния в верхний уровень.

Вложенные layout в React Router делают структуру приложения модульной и предсказуемой, упрощают управление общими элементами интерфейса и создают удобную иерархию маршрутов.