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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 03:52:39