Передача контекста через Outlet

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


Основы работы Outlet и контекста

Компонент Outlet используется в родительском маршруте:

import { Outlet } from 'react-router-dom';

function Dashboard() {
  return (
    <div>
      <h1>Панель управления</h1>
      <Outlet />
    </div>
  );
}

В этом примере Outlet будет заменён компонентом дочернего маршрута в момент навигации. Если требуется передать данные от Dashboard к дочерним компонентам, можно воспользоваться context пропом:

<Outlet context={{ user: currentUser, permissions: userPermissions }} />

Теперь дочерние компоненты смогут получить доступ к этому контексту через хук useOutletContext.


Использование useOutletContext

Хук useOutletContext предоставляет доступ к объекту контекста, переданному через Outlet. Его можно использовать следующим образом:

import { useOutletContext } from 'react-router-dom';

function UserSettings() {
  const { user, permissions } = useOutletContext();
  
  return (
    <div>
      <h2>Настройки пользователя: {user.name}</h2>
      <p>Права доступа: {permissions.join(', ')}</p>
    </div>
  );
}

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

  • Контекст передается только в дочерние маршруты, рендерящиеся через Outlet.
  • Использование useOutletContext упрощает передачу данных, избавляя от необходимости прокидывать пропсы через несколько уровней компонентов.
  • Тип данных контекста может быть любым: объект, массив, функция или примитив.

Динамический контекст для вложенных маршрутов

Контекст может зависеть от состояния родительского компонента. Например:

import { useState } from 'react';
import { Outlet } from 'react-router-dom';

function Dashboard() {
  const [theme, setTheme] = useState('light');

  return (
    <div>
      <h1>Панель управления</h1>
      <button onCl ick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Сменить тему
      </button>
      <Outlet context={{ theme }} />
    </div>
  );
}

Дочерние компоненты автоматически будут получать обновлённый контекст при изменении состояния родителя:

function SettingsPanel() {
  const { theme } = useOutletContext();
  return <div>Текущая тема: {theme}</div>;
}

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


Типизация контекста в TypeScript

При использовании TypeScript можно задать интерфейс для контекста, что повышает безопасность кода:

interface DashboardContext {
  user: { name: string; id: number };
  permissions: string[];
}

const context: DashboardContext = {
  user: { name: 'Иван', id: 1 },
  permissions: ['read', 'write'],
};

<Outlet context={context} />

В дочернем компоненте:

const { user, permissions } = useOutletContext<DashboardContext>();

Типизация позволяет:

  • Отлавливать ошибки доступа к несуществующим полям.
  • Получать автодополнение в редакторе кода.
  • Поддерживать единый контракт данных между родителем и дочерними маршрутами.

Пример вложенной маршрутизации с контекстом

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

function App() {
  const currentUser = { name: 'Анна', id: 42 };
  const userPermissions = ['read', 'write'];

  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Dashboard />}>
          <Route index element={<Home />} />
          <Route path="settings" element={<UserSettings />} />
          <Route path="profile" element={<UserProfile />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

function Dashboard() {
  const currentUser = { name: 'Анна', id: 42 };
  const userPermissions = ['read', 'write'];

  return (
    <div>
      <h1>Панель управления</h1>
      <Outlet context={{ user: currentUser, permissions: userPermissions }} />
    </div>
  );
}

В этом примере все дочерние маршруты (Home, UserSettings, UserProfile) получают одинаковый контекст, что позволяет им использовать данные пользователя и права доступа без лишней передачи пропсов.


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

  • Использовать контекст для маршрутов, где данные нужны сразу нескольким дочерним компонентам.
  • Не перегружать контекст слишком большим количеством данных, чтобы не создавать лишние зависимости.
  • Стараться поддерживать единый формат контекста для всех дочерних маршрутов, чтобы их использование оставалось предсказуемым.
  • Обновление состояния родителя автоматически обновляет контекст у дочерних компонентов, что облегчает управление реактивными интерфейсами.

Контекст через Outlet — мощный инструмент для организации структуры данных в сложных приложениях с вложенной маршрутизацией. Он позволяет создавать чистую и поддерживаемую архитектуру без громоздкой передачи пропсов через несколько уровней компонентов.