You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+TypeScript中如何结合Context与路由实现用户数据持久化及路由保护

解决方案

1. 修复Context数据持久化问题

当前Context仅存于内存,页面刷新就会丢失。我们可以将用户信息同步存储到localStorage(或你正在使用的Cookies),并在AuthProvider初始化时从存储中读取恢复状态。

修改你的AuthContext代码:

import { createContext, useState, useEffect, ReactNode, useContext } from 'react';

type UserData = {
  username: string;
  role: string;
};

type AuthContextType = {
  user: UserData | null;
  login: (username: string, role: string) => void;
  logout: () => void;
};

const AuthContext = createContext<AuthContextType | undefined>(undefined);

type AuthProviderProps = {
  children: ReactNode;
};

export function AuthProvider({ children }: AuthProviderProps) {
  // 初始化时从localStorage读取用户数据
  const [user, setUser] = useState<UserData | null>(() => {
    const savedUser = localStorage.getItem('authUser');
    return savedUser ? JSON.parse(savedUser) : null;
  });

  const login = (username: string, role: string) => {
    const userData = { username, role };
    setUser(userData);
    // 同步存储到localStorage
    localStorage.setItem('authUser', JSON.stringify(userData));
  };

  const logout = () => {
    setUser(null);
    localStorage.removeItem('authUser');
    // 同时清除token
    document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
  };

  // 额外校验:如果token不存在但localStorage有用户数据,说明token过期,自动登出
  useEffect(() => {
    const token = document.cookie.split('; ').find(row => row.startsWith('token='))?.split('=')[1];
    if (!token && user) {
      logout();
    }
  }, [user]);

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

// 自定义hook简化Context调用
export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
}

2. 实现路由权限控制

我们需要创建两个路由守卫组件:

  • RequireAuth:校验用户是否已登录,未登录则跳转到登录页
  • RequireRole:校验用户角色是否匹配,不匹配则跳转到自身角色的仪表盘

2.1 路由守卫组件实现

import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from './AuthContext';

// 未登录拦截
export function RequireAuth() {
  const { user } = useAuth();
  if (!user) {
    return <Navigate to="/login" replace />;
  }
  return <Outlet />;
}

// 角色权限拦截
export function RequireRole({ allowedRoles }: { allowedRoles: string[] }) {
  const { user } = useAuth();
  if (!user) {
    return <Navigate to="/login" replace />;
  }
  // 角色不匹配时跳转到自身角色的仪表盘
  if (!allowedRoles.includes(user.role)) {
    return <Navigate to={`/${user.role}/${user.role}welcome`} replace />;
  }
  return <Outlet />;
}

2.2 修改App路由配置

用路由守卫包裹对应路由,重构后的App组件:

import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { RequireAuth, RequireRole } from './AuthGuards';
import { AuthProvider, useAuth } from './AuthContext';

function App() {
  return (
    <AuthProvider>
      <BrowserRouter>
        <Navigation />
        <Routes>
          {/* 404页面 */}
          <Route path="*" element={<h1>Not found page</h1>} />

          {/* 未登录可访问路由 */}
          <Route path="/" element={<Landing />} />
          <Route path="/home" element={<Home />} />
          <Route path="/login" element={<Login />} />

          {/* 已登录用户统一拦截:未登录无法进入 */}
          <Route element={<RequireAuth />}>
            {/* Officer专属路由 */}
            <Route element={<RequireRole allowedRoles={['officer']} />}>
              <Route path="/officer/*" element={<OfficerPage />}>
                <Route path="officerwelcome" element={<Welcome user="Encargado" />} />
              </Route>
            </Route>

            {/* Manager专属路由 */}
            <Route element={<RequireRole allowedRoles={['manager']} />}>
              <Route path="/manager/*" element={<ManagerPage />}>
                <Route path="managerwelcome" element={<Welcome user="Subgerente" />} />
              </Route>
            </Route>

            {/* Assistant专属路由 */}
            <Route element={<RequireRole allowedRoles={['assistant']} />}>
              <Route path="/assistant/*" element={<AssistantPage />}>
                <Route path="assistantwelcome" element={<Welcome user="Asistente" />} />
              </Route>
            </Route>

            {/* 已登录用户默认跳转:根据角色跳对应仪表盘 */}
            <Route path="/dashboard" element={
              <AuthRouteRedirect />
            } />
          </Route>
        </Routes>
      </BrowserRouter>
    </AuthProvider>
  );
}

// 单独抽离默认跳转组件,避免在App组件中直接调用useAuth报错
function AuthRouteRedirect() {
  const { user } = useAuth();
  return <Navigate to={`/${user?.role}/${user?.role}welcome`} replace />;
}

2.3 优化Login页面逻辑

确保登录成功后先更新Context状态再跳转(原代码中navigate在login之前,可能导致状态未同步完成):

authService
  .login(data)
  .then((response) => {
    Cookies.set("token", response.token, { expires: 1 });
    const { username, role } = response;
    // 先更新Context状态
    login(username, role);
    // 再跳转
    navigate(`/${role}/${role}welcome`, { replace: true });
  })
  .catch((error) => {
    console.log(error.response.data);
  });

3. 是否继续使用Context?

完全可以继续使用Context,你的场景中:

  • 状态结构简单(仅用户信息+登录/登出方法)
  • 状态不需要跨多层组件频繁更新
  • 配合本地存储就能解决持久化问题

除非后续项目复杂度大幅提升(比如需要处理大量异步状态、多模块共享复杂状态),否则无需引入Redux等重型状态管理库,Context完全够用。

内容的提问来源于stack exchange,提问作者Nestor Gonzalez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 16:05:43