Вложенные layouts

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


Основы создания layout

Layout — это компонент, который не привязан к конкретному URL, а служит оберткой для группы маршрутов. В TanStack Router layout определяется аналогично обычному маршруту, но вместо component обычно используется element или loader, чтобы обернуть дочерние маршруты. Пример базовой структуры:

import { createRouter, Route } from '@tanstack/router';
import AppLayout from './layouts/AppLayout';
import Dashboard from './pages/Dashboard';
import Settings from './pages/Settings';

const router = createRouter({
  routeTree: new Route({
    path: '/',
    element: <AppLayout />, // Основной layout
    children: [
      new Route({ path: 'dashboard', element: <Dashboard /> }),
      new Route({ path: 'settings', element: <Settings /> }),
    ],
  }),
});

В этом примере AppLayout является корневым layout, который оборачивает все дочерние страницы, предоставляя общую навигацию или структуру.


Вложенные layouts и их преимущества

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

  1. Повторное использование компонентов: общий header, sidebar, footer для группы страниц.
  2. Изоляция логики: каждый layout может содержать собственные состояния и эффекты.
  3. Упрощение маршрутов: дочерние маршруты наследуют путь и контекст родителя.

Пример вложенных layouts:

const router = createRouter({
  routeTree: new Route({
    path: '/',
    element: <AppLayout />,
    children: [
      new Route({
        path: 'dashboard',
        element: <DashboardLayout />,
        children: [
          new Route({ path: 'analytics', element: <AnalyticsPage /> }),
          new Route({ path: 'reports', element: <ReportsPage /> }),
        ],
      }),
      new Route({ path: 'settings', element: <Settings /> }),
    ],
  }),
});

Здесь DashboardLayout оборачивает только дочерние страницы analytics и reports, сохраняя отдельную структуру от главного AppLayout.


Работа с outlet

Для отображения дочерних маршрутов внутри layout используется компонент Outlet. Он служит как место вставки дочернего контента:

import { Outlet } from '@tanstack/router';

function DashboardLayout() {
  return (
    <div className="dashboard-layout">
      <Sidebar />
      <main>
        <Outlet /> {/* Здесь будут отображаться дочерние маршруты */}
      </main>
    </div>
  );
}

Outlet автоматически рендерит активный дочерний маршрут, сохраняя структуру родительского layout.


Использование loaders и context в layouts

Layouts могут включать загрузчики данных (loader) и передавать контекст в дочерние маршруты. Это особенно полезно для авторизации, получения данных пользователя или настройки темы:

const router = createRouter({
  routeTree: new Route({
    path: '/',
    element: <AppLayout />,
    loader: async () => {
      const user = await fetchUser();
      return { user };
    },
    children: [
      new Route({
        path: 'dashboard',
        element: <DashboardLayout />,
        loader: async ({ parentData }) => {
          const { user } = parentData;
          const projects = await fetchProjects(user.id);
          return { projects };
        },
        children: [
          new Route({ path: 'analytics', element: <AnalyticsPage /> }),
        ],
      }),
    ],
  }),
});

В этом примере дочерний layout получает данные родителя через parentData, что упрощает передачу информации между уровнями маршрутизации.


Управление переходами и анимацией

Используя вложенные layouts, можно создавать плавные анимации переходов между страницами, не перезагружая весь интерфейс. Поскольку каждый layout рендерит только свою часть, дочерние компоненты могут изменяться независимо, позволяя использовать:

  • CSS-переходы для main части layout
  • React Transition Group или Framer Motion для отдельных Outlet
import { motion } from 'framer-motion';
import { Outlet } from '@tanstack/router';

function DashboardLayout() {
  return (
    <div className="dashboard-layout">
      <Sidebar />
      <motion.main initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
        <Outlet />
      </motion.main>
    </div>
  );
}

Динамические маршруты и layouts

TanStack Router поддерживает динамические сегменты URL внутри layouts. Это позволяет создавать многоуровневую маршрутизацию с параметрами:

new Route({
  path: 'projects/:projectId',
  element: <ProjectLayout />,
  children: [
    new Route({ path: 'overview', element: <ProjectOverview /> }),
    new Route({ path: 'tasks', element: <ProjectTasks /> }),
  ],
});

ProjectLayout может использовать useParams для получения projectId и предоставления данных всем дочерним маршрутам через контекст.


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