Координация с state management

React Router обеспечивает маршрутизацию внутри приложений на React, предоставляя возможности навигации, управления историей и синхронизации URL с состоянием приложения. Для приложений с комплексной логикой состояния часто необходимо согласовать маршруты с state management, таким как Redux, Zustand или Recoil, чтобы маршрутизация и состояние работали согласованно.


Синхронизация URL с глобальным состоянием

Поддержка единого источника правды требует, чтобы состояние приложения отражало текущий маршрут. Например, если в Redux хранится активная вкладка или фильтры списка, необходимо обновлять эти значения при изменении маршрута и наоборот.

Пример использования Redux с React Router:

import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useLocation, useNavigate } from 'react-router-dom';
import { setActiveTab } from './store/actions';

function TabSync() {
  const location = useLocation();
  const navigate = useNavigate();
  const dispatch = useDispatch();
  const activeTab = useSelector(state => state.ui.activeTab);

  useEffect(() => {
    const pathToTab = {
      '/home': 'home',
      '/profile': 'profile',
      '/settings': 'settings'
    };
    dispatch(setActiveTab(pathToTab[location.pathname]));
  }, [location.pathname, dispatch]);

  useEffect(() => {
    const tabToPath = {
      home: '/home',
      profile: '/profile',
      settings: '/settings'
    };
    if (tabToPath[activeTab] !== location.pathname) {
      navigate(tabToPath[activeTab]);
    }
  }, [activeTab, navigate, location.pathname]);

  return null;
}

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

  • Использование useLocation для отслеживания текущего пути.
  • Диспатч действия для синхронизации состояния.
  • Обратная синхронизация: изменение глобального состояния приводит к навигации.

Управление параметрами маршрута через state

Маршруты часто содержат параметры (/users/:id), которые могут быть напрямую связаны с состоянием. Это особенно важно для форм, фильтров и страниц деталей.

import { useParams } from 'react-router-dom';
import { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { loadUser } from './store/actions';

function UserDetail() {
  const { id } = useParams();
  const dispatch = useDispatch();

  useEffect(() => {
    if (id) {
      dispatch(loadUser(id));
    }
  }, [id, dispatch]);

  return null;
}

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

  • Параметры маршрута должны триггерить загрузку данных или обновление состояния.
  • Использование useParams позволяет достичь реактивного поведения при смене URL.

Согласованная навигация с middleware

В сложных приложениях полезно использовать middleware (например, в Redux) для управления побочными эффектами при навигации. Это позволяет централизованно обрабатывать переходы и состояние.

const navigationMiddleware = store => next => action => {
  if (action.type === 'NAVIGATE') {
    const { path } = action.payload;
    window.history.pushState({}, '', path);
  }
  return next(action);
};

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

  • Middleware позволяет контролировать все переходы из одного места.
  • Можно комбинировать с Saga или Thunk для асинхронной логики.

Ленивая загрузка компонентов и state

React Router поддерживает React.lazy и Suspense для динамической загрузки компонентов, что важно при интеграции с глобальным состоянием:

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

const Dashboard = lazy(() => import('./Dashboard'));
const Reports = lazy(() => import('./Reports'));

function AppRoutes() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/reports" element={<Reports />} />
      </Routes>
    </Suspense>
  );
}

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

  • Состояние может быть загружено перед рендером компонента.
  • Асинхронные действия должны быть синхронизированы с маршрутом, чтобы избежать состояния, не соответствующего текущему URL.

Управление query-параметрами через state

Query-параметры (?filter=active) тесно связаны с состоянием фильтров и пагинации:

import { useSearchParams } from 'react-router-dom';
import { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { setFilter } from './store/actions';

function FilterSync() {
  const [searchParams, setSearchParams] = useSearchParams();
  const dispatch = useDispatch();

  useEffect(() => {
    const filter = searchParams.get('filter') || 'all';
    dispatch(setFilter(filter));
  }, [searchParams, dispatch]);

  const updateFilter = (filter) => {
    setSearchParams({ filter });
  };

  return null;
}

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

  • useSearchParams обеспечивает двустороннюю синхронизацию состояния и URL.
  • Изменение состояния должно автоматически обновлять query-параметры, и наоборот.

Навигация с контролем состояния и блокировкой переходов

React Router предоставляет usePrompt или unstable_useBlocker для предотвращения потери данных при навигации:

import { useNavigate, useBlocker } from 'react-router-dom';
import { useState } from 'react';

function FormPage() {
  const [isDirty, setIsDirty] = useState(false);
  useBlocker((tx) => {
    if (isDirty) {
      const confirmLeave = window.confirm('Есть несохраненные изменения. Продолжить?');
      if (confirmLeave) tx.retry();
    } else {
      tx.retry();
    }
  });

  return null;
}

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

  • Контроль переходов позволяет синхронизировать пользовательское состояние с навигацией.
  • Необходимо избегать конфликтов между состоянием и URL.

Эти практики обеспечивают надежную интеграцию React Router с state management, позволяя построить приложения, где URL и состояние всегда согласованы, асинхронные данные загружаются своевременно, а навигация контролируется централизованно.