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

React Router v6 Outlet致会话数据重置问题求助

问题:ProtectedRoutes中Outlet导致全局Store会话数据丢失的原因及解决办法

问题重现

App.tsx核心代码

const App = () => {
  return (
    <div className={styles.app}>
      <Store>
        <Header />
        <SessionMgr />
        <Banner />
        <Routing />
        <Footer />
      </Store>
    </div>
  );
};

Routing组件精简版

const Routing = () => {
  return (
    <Routes>
      <Route path='/' element={<Home />} />
      <Route path='/about' element={<About />} />
      <Route path='/signin' element={<SignIn />} />

      <Route element={<ProtectedRoutes />}>
        <Route path='/settings' element={<Settings />} />
      </Route>

      <Route path="*" element={<NotFound />} />
    </Routes>
  );
};

ProtectedRoutes组件实现

const ProtectedRoutes = () => {
  const sessionContainer = Session.useContainer();
  const isAuthenticated = sessionContainer.isLoggedIn();
  console.log(isAuthenticated, sessionContainer.state.user);
  return (isAuthenticated ? <Outlet /> : <Navigate to='/signin' />);
};

用户登录后isAuthenticated为true,但进入Settings页面时,Outlet会清空全局Store会话数据,最终跳转到SignIn页,效果如同刷新浏览器,其他页面跳转无此问题。

可能原因

  • 竞态条件:ProtectedRoutes首次渲染时,会话状态可能尚未完成初始化(比如异步读取本地存储或接口校验),此时isAuthenticated为false触发跳转,后续状态更新时页面已完成跳转,导致Store被意外重置。
  • Store组件意外重挂载:检查Store组件实现,若其内部存在路由切换时的重挂载逻辑(比如被路由组件包裹、状态初始化未做持久化),会导致全局状态被重置。
  • Session容器实例异常:Session.useContainer()可能每次调用都返回新的容器实例,或isLoggedIn()方法逻辑错误,导致登录状态判断失效。

解决办法

1. 处理竞态条件,添加加载状态

等待会话初始化完成后再进行权限判断:

import { useState, useEffect } from 'react';
import { Outlet, Navigate } from 'react-router-dom';

const ProtectedRoutes = () => {
  const sessionContainer = Session.useContainer();
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    // 适配异步初始化场景,比如从本地存储读取会话
    const initSession = async () => {
      await sessionContainer.restoreSession(); // 假设存在会话恢复方法
      setIsLoading(false);
    };
    initSession();
  }, [sessionContainer]);

  if (isLoading) {
    return <div>加载中...</div>; // 替换为项目的加载组件
  }

  const isAuthenticated = sessionContainer.isLoggedIn();
  return isAuthenticated ? <Outlet /> : <Navigate to='/signin' />;
};

2. 确保Store组件稳定挂载

  • 确认Store在App最外层,未被路由组件包裹,避免路由切换触发卸载重挂载。
  • 检查Store内部状态初始化逻辑,将状态初始化放在useEffect(() => {}, [])中,确保仅在首次挂载时执行,而非每次渲染都重置:
// Store组件示例优化
const Store = ({ children }) => {
  const [state, setState] = useState({});

  useEffect(() => {
    // 仅在首次挂载时初始化状态,比如从本地存储读取
    const savedState = localStorage.getItem('appState');
    if (savedState) {
      setState(JSON.parse(savedState));
    }
  }, []);

  return <StoreContext.Provider value={{ state, setState }}>{children}</StoreContext.Provider>;
};

3. 修复Session容器实现

确保useContainer()返回全局单例实例,且isLoggedIn()逻辑正确:

// Session容器实现示例
class SessionContainer {
  private state = { user: null };

  isLoggedIn() {
    return !!this.state.user; // 正确判断用户是否存在
  }

  // 其他方法:login、logout、restoreSession等
}

// 创建全局单例
const globalSessionContainer = new SessionContainer();

export const Session = {
  useContainer: () => globalSessionContainer,
};

4. 改用useNavigate控制跳转时机

避免直接渲染Navigate导致的时机问题:

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

const ProtectedRoutes = () => {
  const sessionContainer = Session.useContainer();
  const navigate = useNavigate();
  const isAuthenticated = sessionContainer.isLoggedIn();

  useEffect(() => {
    if (!isAuthenticated) {
      navigate('/signin', { replace: true });
    }
  }, [isAuthenticated, navigate]);

  if (!isAuthenticated) {
    return null; // 或加载组件
  }

  return <Outlet />;
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 02:01:00