React Router中createBrowserRouter loader函数触发无限循环问题
无限循环原因分析与解决方案
原因分析
- 路由实例频繁重建:
AppRoutes组件每次渲染时,只要Context中的isError、errorMessage或getSubmission发生变化,就会调用createRouter生成全新的路由实例。RouterProvider检测到路由实例更新后,会重新初始化整个路由系统,触发loader函数再次执行。 - 状态更新触发组件重渲染:loader执行时调用的
getSubmission在API失败时会调用setIsError(true),5秒后又调用setIsError(false),这两次状态变更都会导致AppRoutes组件重渲染,进而重建路由实例、触发loader,形成循环。 - 函数引用不稳定:
getSubmission函数在Context所在组件每次渲染时都会重新创建,作为参数传入createRouter后,会导致路由实例被判定为“新对象”,触发路由重新初始化。
解决方案
1. 缓存路由实例,避免不必要重建
用useMemo包裹createRouter的调用,只在必要的依赖项变化时才生成新的路由实例:
export const AppRoutes: React.FC<{ baseRoute?: string }> = ({ baseRoute }) => { const { errorMessage, getSubmission, isError, } = useContext(AppContext) as SelectedContextType const currentUser = getCurrentUser() // 用useMemo缓存路由实例,依赖项只保留必要的稳定值 const router = useMemo(() => createRouter( baseRoute ?? '', currentUser, errorMessage, getSubmission, isError ), [baseRoute, currentUser, getSubmission]) // 移除isError和errorMessage,避免状态变化触发路由重建 return ( <RouterProvider router={router} /> ) }
2. 稳定getSubmission函数的引用
在Context中用useCallback包裹getSubmission,确保其引用不会随组件渲染而变化:
const [isError, setIsError] = useState(false) const [errorMessage, setErrorMessage] = useState('Error') // 用useCallback稳定函数引用 const getSubmission = useCallback(async (appTypeId: string, submissionId: string) => { try { const response = await commonAxios.get( `/api/GetSubmissionInfo?appTypeId=${appTypeId}&submissionId=${submissionId}` ) return response.data } catch (error: any) { setIsError(true) setErrorMessage(error.message) setTimeout(() => setIsError(false), 5000) } }, []) // 依赖项为空,因为内部用到的setIsError、setErrorMessage是useState返回的稳定函数
3. 分离loader的职责,避免直接修改Context状态
loader的核心职责是获取数据,错误处理应该交给路由的errorElement或者组件内部处理,避免在loader中直接修改全局状态:
// 修改loader,让它抛出错误而不是直接修改Context loader: async ({ params }) => { const { appTypeId, submissionId } = params as ReferralParams try { const data = await defaulNewAPIs( appTypeId, currentUser?.username || 'User Name Not Found', submissionId, getSubmission ) return data } catch (error) { // 抛出错误,让React Router的errorElement处理 throw error } } // 在路由中配置errorElement { path: '/:appTypeId/:submissionId/:referralMasterId?', element: <AppLayout ... />, loader: ..., errorElement: <ErrorPage />, // 自定义错误页面组件,在里面处理错误状态 children: [...] }
内容的提问来源于stack exchange,提问作者user11631308
相关产品推荐
相关产品推荐

