Синхронизация с Redux

TanStack Router предоставляет мощные возможности для организации маршрутизации в приложениях на JavaScript и TypeScript. Одной из ключевых особенностей современных SPA является возможность интеграции состояния маршрутизатора с глобальным состоянием приложения. Redux, как популярный инструмент управления состоянием, может эффективно взаимодействовать с TanStack Router для синхронизации URL, состояния маршрутов и данных компонентов.

Основы интеграции маршрутизатора с Redux

Для синхронизации TanStack Router с Redux необходимо учитывать два аспекта:

  1. Состояние маршрутизатора – текущий путь, параметры маршрута и состояния навигации.
  2. Глобальное состояние приложения – данные, которые зависят от маршрута или должны сохраняться при навигации.

TanStack Router не имеет встроенной зависимости от Redux, но его API позволяет легко создавать события и подписки, которые интегрируются с Redux.

Подключение маршрутизатора к Redux

Простейший способ интеграции – создание middleware или listener для действий маршрутизатора. Основные шаги:

  1. Создание хранилища Redux с базовой конфигурацией:
import { configureStore } from '@reduxjs/toolkit';
import routerReducer from './routerSlice';

export const store = configureStore({
  reducer: {
    router: routerReducer
  }
});
  1. Определение slice для хранения состояния маршрутизатора:
import { createSlice } from '@reduxjs/toolkit';

const routerSlice = createSlice({
  name: 'router',
  initialState: {
    pathname: '/',
    params: {},
    search: ''
  },
  reducers: {
    setRouteState(state, action) {
      const { pathname, params, search } = action.payload;
      state.pathname = pathname;
      state.params = params;
      state.search = search;
    }
  }
});

export const { setRouteState } = routerSlice.actions;
export default routerSlice.reducer;
  1. Подключение TanStack Router к Redux через onNavigate:
import { createRouter } from '@tanstack/router';
import { store } from './store';
import { setRouteState } from './routerSlice';

const router = createRouter({
  routes: [
    // определения маршрутов
  ],
  onNavigate: (context) => {
    store.dispatch(setRouteState({
      pathname: context.location.pathname,
      params: context.match?.params || {},
      search: context.location.search
    }));
  }
});

Двусторонняя синхронизация состояния

Чтобы не только обновлять Redux при навигации, но и позволять изменять маршрут через Redux, можно создать middleware, который будет реагировать на изменения состояния маршрута:

store.subscribe(() => {
  const { pathname, params, search } = store.getState().router;
  const currentPath = router.state.location.pathname;

  if (currentPath !== pathname) {
    router.navigate({
      to: pathname,
      params,
      search
    });
  }
});

Такой подход обеспечивает полную синхронизацию между состоянием Redux и маршрутизатором, предотвращая рассинхронизацию URL и состояния приложения.

Сценарии использования

  1. Сохранение выбранной вкладки или фильтров – состояние фильтров на странице может храниться в Redux и синхронизироваться с URL через TanStack Router.
  2. Восстановление состояния при обновлении страницы – Redux хранит актуальное состояние маршрута, которое при перезагрузке страницы можно использовать для навигации.
  3. Отложенная навигация – действия в Redux могут инициировать переход на другой маршрут с дополнительной логикой.

Рекомендации по производительности

  • Избегать циклических обновлений: при двусторонней синхронизации важно проверять текущее состояние маршрутизатора и Redux перед вызовом navigate.
  • Использовать мемоизацию: при работе с параметрами маршрута и большим количеством данных рекомендуется мемоизировать вычисляемые значения для снижения нагрузки на render.
  • Разделять состояние маршрута и бизнес-данные: хранение в Redux только критичного состояния маршрута предотвращает избыточные ререндеры компонентов.

Работа с параметрами и query

TanStack Router предоставляет удобные методы для работы с параметрами маршрута и query-параметрами. При синхронизации с Redux можно хранить их в объекте params и search. Для преобразования query строки в объект удобно использовать URLSearchParams:

const searchParams = Object.fromEntries(new URLSearchParams(location.search));
store.dispatch(setRouteState({
  pathname: location.pathname,
  params: routeParams,
  search: searchParams
}));

Интеграция с TypeScript

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

interface RouterState {
  pathname: string;
  params: Record<string, string>;
  search: Record<string, string>;
}

const initialState: RouterState = {
  pathname: '/',
  params: {},
  search: {}
};

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

Итоговые принципы

  • Состояние маршрута должно быть единым источником правды, которое синхронизируется с Redux.
  • Любые изменения состояния маршрута через TanStack Router должны обновлять Redux.
  • Изменения через Redux должны корректно вызывать navigate, избегая циклических обновлений.
  • Чёткое разделение состояния маршрута и бизнес-логики обеспечивает производительность и предсказуемое поведение приложения.

Эти подходы позволяют создавать сложные SPA с TanStack Router и Redux, сохраняя согласованность состояния, URL и логики приложения.