React路由内联if-else执行两次 重复重定向问题排查
问题根因
- 异步鉴权和初始渲染存在时序差:
isAuthenticated的初始值被设为false,组件首次挂载时,useEffect触发的后端token校验请求是异步逻辑,还未返回结果,这轮渲染会直接命中三元表达式的else分支,写在else里的日志、重定向逻辑会在这时候执行。等异步请求拿到结果,调用setIsAuthenticated(true)更新状态,组件触发第二轮重渲染,才会命中if分支渲染对应页面,因此会观察到两个分支先后触发。注意:两个分支不是同一次渲染中重复执行,是两次独立渲染分别命中了不同判断结果。 - React 18+开发环境严格模式放大异常:通过官方脚手架新建的React 18项目,入口文件默认会用
<StrictMode>包裹根组件,开发环境下会执行一次额外的挂载-卸载-重挂载流程,空依赖数组的useEffect会触发两次,导致鉴权请求重复发起、状态多更新一轮,进一步引发重复重定向的问题。 - 代码写法不规范:路由的
element属性要求传入合法React节点,原代码在dashboard路由的else分支直接写console.log,其返回值是undefined,本身就不符合React渲染规则;首次渲染未拿到鉴权结果就直接触发Navigate重定向,等状态更新后又要跳转回目标路由,自然会出现重复跳转的异常。
修复方案
核心是新增鉴权加载的中间状态,避免鉴权完成前就执行路由分支判断:
- 新增
loading状态标识鉴权进度,初始值设为true,等鉴权接口返回结果(无论成功、失败),都将loading置为false,鉴权过程中统一渲染加载提示,不走路由判断逻辑。 - 修正所有路由分支的返回值,确保三元表达式的两个分支都返回合法React节点,禁止在
element属性位置直接写无节点返回的执行语句。 - (非必要不推荐)如果不需要严格模式的开发校验,可以在项目入口文件移除
<StrictMode>的包裹,该方式只能掩盖开发环境的重复执行问题,无法解决生产环境下初始渲染和异步鉴权的时序差问题。
修复后的核心代码
import React, { Fragment, useState, useEffect } from "react"; import './App.css'; import "react-toastify/dist/ReactToastify.css"; import {BrowserRouter as Router, Routes, Route, Navigate} from "react-router-dom"; import { toast } from "react-toastify"; //components import Login from "./components/Login"; import Register from "./components/Register"; import Dashboard from "./components/Dashboard"; import Home from "./components/Home"; import Auction from "./components/Auction"; function App() { const [isAuthenticated, setIsAuthenticated] = useState(false); // 新增鉴权加载状态 const [authLoading, setAuthLoading] = useState(true); const checkAuthenticated = async () => { try { const res = await fetch("http://localhost:5000/auth/is-verify", { method: "GET", headers: { token: localStorage.token } }); const parseRes = await res.json(); setIsAuthenticated(parseRes === true); } catch (err) { console.error(err.message); setIsAuthenticated(false); } finally { // 鉴权流程结束,关闭加载状态 setAuthLoading(false); } }; useEffect(() => { checkAuthenticated(); }, []); const setAuth = boolean => { setIsAuthenticated(boolean); }; // 鉴权未完成时渲染加载提示,不执行路由判断 if (authLoading) { return <div className="loading">权限校验中...</div> } return ( <Fragment> <Router> <div className="container"> <Routes> <Route path='/home' element = { isAuthenticated ? ( <Home setAuth={setAuth} /> ) : ( <Navigate to="/login" /> ) } /> <Route path='/login' element = { !isAuthenticated ? ( <Login setAuth={setAuth} /> ) : ( <Navigate to="/home" /> ) } /> <Route path='/register' element = { !isAuthenticated ? ( <Register setAuth={setAuth} /> ) : ( <Navigate to="/home" /> ) } /> <Route path='/dashboard' element = { isAuthenticated ? ( <Dashboard setAuth={setAuth} /> ) : ( <Navigate to="/login" /> ) } /> <Route path='/auction' element = {<Auction setAuth={setAuth} />} /> </Routes> </div> </Router> </Fragment> ); }; export default App;
优化建议:后续可以把鉴权判断逻辑抽离成通用的
PrivateRoute、PublicRoute包装组件,不用在每个路由下重复写三元判断,可维护性更高。
内容的提问来源于stack exchange,提问作者Dimo
相关产品推荐
相关产品推荐

