React Router刷新页面时组件多次调用渲染问题排查
问题现象
刷新页面时组件被多次调用,挂载逻辑重复触发,相关代码和日志如下:
路由配置文件 MainLayout.tsx
import { FC, ReactElement, useEffect } from 'react' import { Routes, Route, BrowserRouter } from 'react-router-dom' import { createBrowserHistory } from 'history' import { useLocation } from 'react-router-dom' import { Navigate } from 'react-router-dom' import { Outlet } from 'react-router-dom' import { IntroductionPage } from '../pages/introduction/introduction-page' import { useTranslation } from 'react-i18next' import { storage } from '../utils/storage' export const history = createBrowserHistory() export const MainLayout: FC = () => { const { t } = useTranslation() useEffect(() => { console.log('MainLayout:: constructor') }, []) const RequireAuth = (): ReactElement => { const { token } = storage.getState().authReducer let location = useLocation() if (!token) return <Navigate to="/login" state={{ from: location }} /> return <Outlet /> } return ( <BrowserRouter history={history}> <div className="main-wrapper"> <div className="content-wrapper"> <div className="main-content"> <Routes> <Route path="/" element={<Login />} /> <Route path="/login" element={<Login />} /> <Route element={<RequireAuth />}> <Route path="/acceptance" element={<AcceptancePage />} /> <Route path="/introduction/:page" element={<IntroductionPage />} /> </Route> </Routes> </div> </div> </div> </BrowserRouter> ) }
介绍页逻辑 IntroductionPage
useEffect(() => { console.log('IntroductionPage:: constructor') setIntroduction(introductions[+page - 1]) }, [])
控制台异常日志
刷新介绍页时输出:
IntroductionPage:: constructor MainLayout.tsx:23 MainLayout:: constructor IntroductionPage:: constructor IntroductionPage:: constructor
问题根因
三个问题共同导致组件重复挂载:
- 最核心的问题:
RequireAuth路由守卫组件被定义在MainLayout组件内部。每次MainLayout触发重渲染,都会生成一个全新引用的RequireAuth函数,React做组件diff时会判定这是完全不同的组件类型,直接卸载旧的RequireAuth下挂载的所有子路由(包括IntroductionPage),再重新挂载新组件,直接导致子组件的挂载副作用重复执行。 - 如果使用React 18开发环境,默认开启的严格模式会在开发环境对组件执行「挂载-卸载-重新挂载」的双调用流程,用来提前检测不规范的副作用写法,这会额外触发一次组件挂载日志,生产环境不会出现该双调用行为。
BrowserRouter的props传参错误:react-router-dom v6版本的BrowserRouter本身不支持history入参,自定义history实例需要使用官方提供的HistoryRouter,错误传入不识别的props会导致路由内部状态同步异常,触发额外的重渲染和重挂载流程。
修复方式
- 将
RequireAuth组件移动到MainLayout组件外部定义,禁止在组件渲染函数内部声明新的子组件,避免组件引用随渲染变化导致的整棵子树重挂载。 - 移除
BrowserRouter上错误传入的history属性,如果需要全局操作路由跳转,直接使用useNavigate钩子即可,不要依赖外部导出的history实例。 - 介绍页的useEffect存在闭包陷阱:空依赖数组内使用了
page变量,后续page变化时不会触发介绍内容更新,需要将page加入useEffect的依赖数组。 - 若不需要开发环境的严格模式双检测,可以在项目入口文件移除
<StrictMode>包裹,不建议为了规避日志直接关闭,该检测能提前暴露很多副作用写法问题。
内容的提问来源于stack exchange,提问作者Israel
相关产品推荐
相关产品推荐

