Координация состояния роутера и store

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

Состояние роутера как источник истины

В TanStack Router состояние роутера включает в себя:

  • Текущий путь (pathname) – строка, отображающая текущий URL.
  • Параметры маршрута (params) – объект с динамическими сегментами URL.
  • Квери-параметры (search) – объект с параметрами запроса.
  • Стейт (state) – объект, передаваемый через навигацию, аналог history.state в браузере.

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

Интеграция с глобальными store

Для интеграции TanStack Router с глобальными store, например, Redux, Zustand или Jotai, следует соблюдать несколько принципов:

  1. Инициализация состояния из URL При загрузке страницы store должен получать значения, закодированные в URL. Например, текущий фильтр таблицы или сортировка могут быть извлечены из search:

    const currentFilters = parseSearch(router.state.search);
    store.setFilters(currentFilters);
  2. Синхронизация store с изменением маршрута Использование хука useRouter или подписка на события роутера позволяет реагировать на изменения URL:

    useEffect(() => {
      const unsubscribe = router.subscribe(({ search, params }) => {
        store.setFilters(parseSearch(search));
        store.setItemId(params.id);
      });
      return unsubscribe;
    }, [router]);
  3. Обновление URL при изменении store Когда данные в store меняются, важно обновлять URL, чтобы пользователь мог делиться ссылкой с актуальным состоянием:

    useEffect(() => {
      const search = serializeFilters(store.filters);
      router.navigate({ search });
    }, [store.filters]);

Координация вложенных роутов и локального состояния

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

  • Хранить локальное состояние в дочерних store или атомах, привязанных к конкретному роуту.

  • Использовать route.id или route.path как ключ для синхронизации состояния:

    const routeKey = router.state.currentRoute.id;
    const localState = localStore.getState(routeKey);
  • Поддерживать обратную синхронизацию, чтобы изменения локального состояния отражались в URL и обратно:

    useEffect(() => {
      const search = serializeLocalState(localState);
      router.navigate({ search }, { replace: true });
    }, [localState]);

Асинхронные действия и загрузка данных

TanStack Router предоставляет хуки loader и onLoadError для асинхронной загрузки данных, которые можно интегрировать с store:

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

    const userLoader = async ({ params }) => {
      const user = await api.fetchUser(params.userId);
      store.setUser(user);
      return user;
    };
  • Обработка ошибок и fallback: синхронизация store с ошибкой позволяет управлять состоянием приложения без нарушения маршрута:

    const onEr ror = (error) => {
      store.setError(error.message);
    };

Управление сложными состояниями фильтров и пагинации

Для компонентов, таких как таблицы с фильтрацией и пагинацией, TanStack Router совместно с store позволяет:

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

  • Поддерживать согласованную сортировку и страницу при навигации между маршрутами.

  • Обновлять store при изменении URL и наоборот, избегая бесконечных циклов через проверку актуальности значений:

    if (!isEqual(router.state.search, serializeFilters(store.filters))) {
      router.navigate({ search: serializeFilters(store.filters) });
    }

Практика: двухсторонняя синхронизация

Основной паттерн интеграции роутера и store — двусторонняя синхронизация:

  1. При загрузке маршрута извлекать параметры из URL и записывать их в store.
  2. При изменении store обновлять URL, используя методы router.navigate.
  3. Проверять актуальность состояния, чтобы избежать повторной записи и рекурсивной навигации.
  4. Для вложенных маршрутов и компонентов использовать отдельные локальные хранилища, привязанные к route id.

Эта схема обеспечивает:

  • Полную прозрачность состояния приложения через URL.
  • Возможность делиться ссылками с сохраненным состоянием.
  • Централизованное управление данными через store при сохранении независимости компонентов.

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