Передача данных в layouts

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


Определение данных в layouts

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

import { createRouter, createRoute } from '@tanstack/router';

const layoutRoute = createRoute({
  path: '/dashboard',
  component: DashboardLayout,
  loader: async () => {
    const userData = await fetch('/api/user').then(res => res.json());
    return { userData };
  },
  children: [
    // дочерние маршруты
  ],
});

Ключевые моменты:

  • loader вызывается до рендеринга компонента layout.
  • Любой объект, возвращённый loader’ом, становится доступным через useLoaderData().

Доступ к данным layout в дочерних маршрутах

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

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

function UserProfile() {
  const { userData } = useLoaderData({ from: 'dashboard' });
  
  return (
    

{userData.name}

{userData.email}

); }

Особенности работы:

  • useLoaderData({ from: 'layoutId' }) позволяет указать конкретный layout, из которого требуется взять данные.
  • Данные layout сохраняются в контексте роутера и обновляются только при повторной активации маршрута.

Передача параметров маршрута в layout

Layouts могут использовать параметры URL, определённые на их уровне, для загрузки данных:

const layoutRoute = createRoute({
  path: '/projects/:projectId',
  component: ProjectLayout,
  loader: async ({ params }) => {
    const project = await fetch(`/api/projects/${params.projectId}`).then(r => r.json());
    return { project };
  },
  children: [
    { path: 'tasks', component: ProjectTasks },
  ],
});
  • Параметры URL доступны через params в loader.
  • Это позволяет динамически загружать данные, специфичные для текущего маршрута.

Передача данных через context

TanStack Router также поддерживает передачу дополнительного контекста через layout:

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

function ProjectLayout() {
  const match = useMatch('/projects/:projectId');
  
  const contextValue = {
    projectId: match.params.projectId,
    permissions: ['read', 'write'],
  };

  return (
    
      
    
  );
}
  • Контекст позволяет дочерним маршрутам использовать данные без обращения к loader.
  • Подходит для данных, которые не требуют асинхронной загрузки, например, пользовательские настройки или права доступа.

Совмещение loader и context

Часто используется комбинация loader и context, чтобы добавить гибкость и контроль над данными:

function ProjectLayout() {
  const { project } = useLoaderData();
  const permissions = ['read', 'write']; // статические данные

  return (
    
      
    
  );
}
  • Loader загружает динамические данные с сервера.
  • Context предоставляет дополнительные значения, не зависящие от маршрута.
  • Дочерние маршруты получают доступ и к серверным данным, и к локальному контексту.

Динамическая передача данных между layouts

В сложных приложениях могут использоваться вложенные layouts, где данные одного layout становятся входными для следующего:

const rootRoute = createRoute({
  path: '/',
  component: RootLayout,
  loader: () => ({ theme: 'dark' }),
  children: [
    createRoute({
      path: 'dashboard',
      component: DashboardLayout,
      loader: ({ parentData }) => ({
        ...parentData,
        notifications: fetchNotifications(),
      }),
    }),
  ],
});
  • parentData содержит данные из родительского loader.
  • Это обеспечивает цепочку передачи данных, избегая повторной загрузки и упрощая управление состоянием.

Практические рекомендации

  1. Использовать loader для асинхронных данных — это гарантирует, что компонент получит данные до рендеринга.
  2. Передавать данные через context для локальных или производных значений — это снижает количество запросов и повторных вычислений.
  3. Вложенные layouts должны наследовать данные родителя через parentData — это облегчает построение сложных структур с общей информацией.
  4. Минимизировать дублирование данных — один layout должен быть источником истины для определённого набора данных.