Создание переиспользуемых layouts

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

Определение Layout

Layout в TanStack Router создаётся аналогично обычному маршруту, но с возможностью вложенных маршрутов:

import { createRouter, createRoute } from '@tanstack/router';
import { LayoutComponent } from './components/LayoutComponent';
import HomePage from './pages/HomePage';
import AboutPage from './pages/AboutPage';

const rootRoute = createRoute({
  path: '/',
  component: LayoutComponent,
  children: [
    createRoute({
      path: 'home',
      component: HomePage,
    }),
    createRoute({
      path: 'about',
      component: AboutPage,
    }),
  ],
});

const router = createRouter({
  route: rootRoute,
});

В данном примере LayoutComponent является общим каркасом для маршрутов home и about. Любые элементы, включённые в LayoutComponent, будут отображаться на всех дочерних страницах.

Компонент Layout и его особенности

Layout-компонент обычно содержит следующие элементы:

  • Общий хедер (Header)
  • Навигационное меню (Navigation)
  • Общий футер (Footer)
  • Контейнер для рендеринга дочерних маршрутов (Outlet)

Пример реализации Layout-компонента:

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

export default function LayoutComponent() {
  return (
    <div>
      <header>
        <h1>Моё приложение</h1>
        <nav>
          <a href="/home">Главная</a>
          <a href="/about">О нас</a>
        </nav>
      </header>
      <main>
        <Outlet />
      </main>
      <footer>
        © 2026 Компания
      </footer>
    </div>
  );
}

Ключевой момент: <Outlet /> отвечает за рендеринг текущего дочернего маршрута. Без него вложенные страницы не будут отображаться внутри layout.

Переиспользуемость Layout

Layouts особенно полезны, когда требуется одинаковая структура для нескольких маршрутов. Например, если есть несколько разделов приложения с одинаковым хедером и навигацией, можно создать один основной Layout и подключать его ко всем маршрутам.

Можно создавать вложенные layouts, чтобы ещё больше структурировать приложение. Например:

const dashboardLayout = createRoute({
  path: '/dashboard',
  component: DashboardLayout,
  children: [
    createRoute({
      path: 'analytics',
      component: AnalyticsPage,
    }),
    createRoute({
      path: 'settings',
      component: SettingsPage,
    }),
  ],
});

В данном случае DashboardLayout может включать боковую панель (sidebar), уникальную для раздела “dashboard”, но при этом использовать общий <Header> и <Footer> из основного layout.

Динамические Layouts и загрузка данных

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

Пример с загрузкой данных:

const profileLayout = createRoute({
  path: '/profile',
  component: ProfileLayout,
  loader: async () => {
    const user = await fetch('/api/current-user').then(res => res.json());
    return { user };
  },
  children: [
    createRoute({
      path: 'overview',
      component: OverviewPage,
    }),
    createRoute({
      path: 'settings',
      component: ProfileSettingsPage,
    }),
  ],
});

В ProfileLayout можно получить данные через хук useLoaderData():

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

export default function ProfileLayout() {
  const { user } = useLoaderData();

  return (
    <div>
      <header>
        <h2>Добро пожаловать, {user.name}</h2>
      </header>
      <main>
        <Outlet />
      </main>
    </div>
  );
}

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

Состояние и контекст внутри Layout

Layouts также отлично подходят для управления состоянием и предоставления контекста дочерним маршрутам. Например, можно использовать React Context для хранения глобальных настроек или состояния авторизации:

import { createContext, useState } from 'react';
import { Outlet } from '@tanstack/router';

export const ThemeContext = createContext();

export default function AppLayout() {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <header>Моё приложение</header>
      <main>
        <Outlet />
      </main>
    </ThemeContext.Provider>
  );
}

Все дочерние маршруты, рендерящиеся через <Outlet />, смогут использовать ThemeContext, не повторяя настройку состояния на каждой странице.

Оптимизация и lazy-loading Layout

Для крупных приложений можно подключать layout динамически с помощью React.lazy для оптимизации загрузки:

import { lazy } from 'react';
const LazyLayout = lazy(() => import('./LazyLayout'));

const lazyRoute = createRoute({
  path: '/lazy',
  component: LazyLayout,
  children: [
    createRoute({ path: 'page1', component: Page1 }),
  ],
});

Это снижает первоначальный размер бандла и ускоряет загрузку приложения.

Итоговые принципы использования Layouts в TanStack Router

  • Повторное использование: Один layout может обслуживать несколько маршрутов, включая вложенные.
  • Вложенность: Layout может содержать другие layouts для детальной структуры.
  • Контекст и состояние: Layout — отличное место для глобального состояния и провайдеров.
  • Загрузка данных: Layout может предварительно загружать данные, доступные всем дочерним маршрутам.
  • Lazy-loading: Оптимизация больших приложений через динамическую загрузку layout.

Layouts делают структуру приложения модульной, логичной и лёгкой для поддержки. С их помощью разработка становится менее фрагментированной и позволяет сосредоточиться на функциональности отдельных компонентов, не дублируя общие элементы интерфейса.