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

TanStack Router — это современный роутер для React и других фреймворков, обеспечивающий декларативную маршрутизацию, поддержку асинхронных данных и гибкую работу с вложенными маршрутами. Zustand — это лёгкая и эффективная библиотека для управления состоянием в приложениях на React. Их интеграция позволяет строить приложения с централизованным управлением состоянием и согласованной маршрутизацией, сохраняя при этом высокую производительность.


Настройка Zustand

Для начала создаём глобальный стор с Zustand:

import { create } from 'zustand';

export const useStore = create((set) => ({
  user: null,
  isAuthenticated: false,
  setUser: (user) => set({ user, isAuthenticated: !!user }),
  logout: () => set({ user: null, isAuthenticated: false }),
}));

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

  • Состояние приложения хранится в одном объекте, что облегчает доступ к данным из различных компонентов.
  • Методы setUser и logout позволяют обновлять состояние и автоматически пересчитывать связанные значения.

Подключение TanStack Router

Сначала необходимо определить маршруты:

import { createRouter, RouterProvider, Route } from '@tanstack/router';
import HomePage from './pages/HomePage';
import ProfilePage from './pages/ProfilePage';
import LoginPage from './pages/LoginPage';

const routes = [
  new Route({ path: '/', component: HomePage }),
  new Route({ path: '/login', component: LoginPage }),
  new Route({ 
    path: '/profile', 
    component: ProfilePage,
    // Защита маршрута
    beforeLoad: async () => {
      const { isAuthenticated } = useStore.getState();
      if (!isAuthenticated) throw new Error('Unauthorized');
    }
  }),
];

const router = createRouter({ routes });

Особенности интеграции:

  • beforeLoad используется для проверки состояния аутентификации перед загрузкой маршрута.
  • Вызов useStore.getState() позволяет синхронно получать данные состояния вне React-компонента.

Реактивное состояние маршрутов

Zustand позволяет связывать состояние приложения с TanStack Router для динамической маршрутизации. Например, можно автоматически перенаправлять пользователя при изменении состояния аутентификации:

import { useEffect } from 'react';
import { useStore } from './store';
import { useRouter } from '@tanstack/router';

function AuthWatcher() {
  const { isAuthenticated } = useStore();
  const router = useRouter();

  useEffect(() => {
    if (!isAuthenticated) {
      router.navigate('/login');
    }
  }, [isAuthenticated, router]);

  return null;
}

Заметки:

  • Компонент AuthWatcher не рендерит UI, а следит за состоянием и инициирует навигацию.
  • useEffect гарантирует, что редирект произойдёт сразу после изменения состояния.

Асинхронная загрузка данных

TanStack Router позволяет работать с асинхронными загрузками данных, которые могут зависеть от состояния Zustand:

const ProfilePage = () => {
  const { user, setUser } = useStore();

  useEffect(() => {
    if (!user) {
      fetch('/api/profile')
        .then(res => res.json())
        .then(data => setUser(data));
    }
  }, [user, setUser]);

  if (!user) return <div>Загрузка...</div>;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
};

Важные моменты:

  • Zustand обеспечивает централизованное состояние, что упрощает повторное использование данных на разных маршрутах.
  • TanStack Router гарантирует, что асинхронная загрузка может быть согласована с переходом между маршрутами через beforeLoad или onLoad.

Синхронизация состояния и маршрутов

Для сложных приложений можно создать реактивные эффекты, которые связывают состояние и маршрут:

useStore.subscribe(
  (state) => state.isAuthenticated,
  (isAuthenticated) => {
    if (!isAuthenticated) router.navigate('/login');
  }
);

Преимущества:

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

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

  1. Минимизировать дублирование состояния — не хранить один и тот же флаг в нескольких местах.
  2. Использовать селекторы Zustand для выборочного слежения за изменениями состояния, чтобы избежать лишних ререндеров.
  3. Выносить защиту маршрутов в beforeLoad для синхронной проверки состояния перед загрузкой компонента.
  4. Разделять состояние UI и данные — состояние интерфейса (модальные окна, спиннеры) лучше хранить отдельно от глобальных данных пользователя.
  5. Комбинировать подписки Zustand и эффекты TanStack Router для создания мощных реактивных сценариев навигации.

Пример комплексной интеграции

const routes = [
  new Route({ path: '/', component: HomePage }),
  new Route({ path: '/login', component: LoginPage }),
  new Route({ 
    path: '/profile', 
    component: ProfilePage,
    beforeLoad: async () => {
      const { isAuthenticated } = useStore.getState();
      if (!isAuthenticated) throw new Error('Unauthorized');
    },
  }),
];

const router = createRouter({ routes });

function App() {
  return (
    <>
      <RouterProvider router={router} />
      <AuthWatcher />
    </>
  );
}

Особенности конструкции:

  • TanStack Router управляет маршрутизацией и загрузкой компонентов.
  • Zustand обеспечивает централизованное состояние пользователя.
  • AuthWatcher поддерживает согласованность между состоянием и маршрутизацией, создавая полностью реактивное приложение.

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