В TanStack Router организация маршрутов и layout-компонентов позволяет создавать мощные архитектуры с управляемым состоянием. Одной из ключевых возможностей является изоляция состояния в layout-компонентах, что позволяет сохранять и контролировать локальное состояние для всех вложенных маршрутов, не влияя на состояние других веток приложения.
Layout-компонент в TanStack Router выступает контейнером для маршрутов, предоставляя возможность хранить состояние, которое будет доступно всем дочерним маршрутам. Это состояние может включать:
Пример простого layout-компонента с локальным состоянием:
import { useState } from 'react';
import { Outlet } from '@tanstack/react-router';
function DashboardLayout() {
const [theme, setTheme] = useState('light');
return (
<div className={`dashboard ${theme}`}>
<header>
<button onCl ick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Переключить тему
</button>
</header>
<Outlet />
</div>
);
}
export default DashboardLayout;
В этом примере theme хранится на уровне layout и
доступен для всех дочерних маршрутов через контекст или пропсы. Каждое
изменение состояния влияет только на этот layout и его потомков, не
затрагивая другие части приложения.
useLocalLoader и useLocalMutationДля более сложных сценариев, когда состояние должно быть асинхронным
и сохраняться между переходами, TanStack Router предоставляет
локальные загрузчики и мутации
(useLocalLoader, useLocalMutation).
Пример асинхронного состояния на layout:
import { useLocalLoader } from '@tanstack/react-router';
function ProjectsLayout() {
const projects = useLocalLoader({
key: 'projects',
loader: async () => {
const response = await fetch('/api/projects');
return response.json();
},
});
return (
<div>
<h2>Проекты</h2>
{projects.isLoading && <p>Загрузка...</p>}
{projects.data?.map(project => (
<div key={project.id}>{project.name}</div>
))}
</div>
);
}
Преимущества подхода:
isLoading, error).В TanStack Router можно создавать несколько layout-уровней, каждый со своим локальным состоянием. Например:
const rootRoute = createRoute({
path: '/',
component: RootLayout,
children: [
{
path: 'dashboard',
component: DashboardLayout,
children: [
{ path: 'analytics', component: AnalyticsPage },
{ path: 'settings', component: SettingsPage },
],
},
{
path: 'projects',
component: ProjectsLayout,
children: [
{ path: ':projectId', component: ProjectDetailPage },
],
},
],
});
В такой структуре:
DashboardLayout управляет своим локальным состоянием
(например, переключением темы, фильтрами данных аналитики);ProjectsLayout имеет отдельное локальное состояние
(список проектов, состояние загрузки).Изменения в одном layout не затрагивают другой, что обеспечивает чистую изоляцию состояния и предотвращает неожиданные сайд-эффекты.
Для передачи состояния вниз по иерархии TanStack Router рекомендует использовать React Context. Например, если необходимо, чтобы дочерние страницы имели доступ к локальному состоянию layout:
import { createContext, useContext } from 'react';
const DashboardContext = createContext(null);
function DashboardLayout() {
const [theme, setTheme] = useState('light');
return (
<DashboardContext.Provider value={{ theme, setTheme }}>
<Outlet />
</DashboardContext.Provider>
);
}
function useDashboard() {
return useContext(DashboardContext);
}
Дочерние страницы могут получить доступ к состоянию через
useDashboard(), не затрагивая глобальный store.
useLocalLoader и useLocalMutation позволяют
хранить состояние на уровне layout, ускоряя загрузку страниц.TanStack Router автоматически сохраняет состояние layout при навигации между дочерними маршрутами, если layout не размонтируется. Если же навигация приводит к размонтированию layout, состояние теряется, что можно использовать для “сброса” данных при переходе между разделами. Для сохранения состояния при размонтировании можно использовать внешние стораджи или глобальный store.
Хотите, я могу сделать подробную визуальную схему с примером изоляции состояния layout с несколькими маршрутами, чтобы было сразу понятно, где и как хранится состояние?