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

React Router传递子路由致孙组件重复挂载的原因与非Context解法

React Router嵌套路由下孙组件重复挂载问题:成因与非Context解决方案

一、为什么只有ThirdPage会重复挂载?

咱们直接抓核心:ChildRoutes函数的引用每次渲染都在变,这是导致孙组件反复挂载的罪魁祸首。

看你的Routing组件代码,每次它渲染时,都会给上层组件(比如MainPage)传递一个全新的ChildRoutes函数:

<Component {...rest} ChildRoutes={props => <Routing routes={routes} {...props}/>}/>

当MainPage的foo状态变化时,MainPage会重新渲染,这时它传给SecondPage的ChildRoutes已经是一个新的函数引用了。

那为什么SecondPage自己没事?因为SecondPage是被Route组件用component prop直接渲染的,component的值始终是SecondPage这个固定的组件引用——React只要组件类型没变,就会复用现有实例,只更新props,不会卸载重挂载。

但ThirdPage的情况不一样:SecondPage接收到新的ChildRoutes后会重新渲染,此时<ChildRoutes foo={foo} bar={bar} />相当于调用了一个全新的函数组件。React会认为这是完全不同的组件类型,于是直接卸载原来的ThirdPage,再重新挂载新的实例,所以你看到的mount third日志会每次都触发。

二、不用Context的解决办法

方法1:用useCallback缓存ChildRoutes函数

最直接的方式是让ChildRoutes的引用稳定下来,只有当它的依赖项变化时才更新。我们可以在Routing组件里用useCallback来缓存这个函数:

修改后的Routing组件代码:

import React, { useCallback } from 'react';
import { Route, Switch } from 'react-router-dom';

const Routing = ({ routes: passedRoutes, ...rest }) => {
  if (!passedRoutes) return null;

  // 用useCallback缓存函数,依赖项只有passedRoutes(你的路由配置是固定的,所以这个依赖不会变)
  const stableChildRoutes = useCallback((props) => {
    return <Routing routes={passedRoutes} {...props} />;
  }, [passedRoutes]);

  return (
    <Switch>
      {passedRoutes.map(({ routes, component: Component, ...route }) => {
        return (
          <Route key={route.path} {...route}>
            <Component 
              {...rest} 
              ChildRoutes={stableChildRoutes} 
            />
          </Route>
        );
      })}
    </Switch>
  );
};

这样一来,只要你的路由配置passedRoutes不变,stableChildRoutes的引用就会一直稳定。当MainPage状态变化时,传给SecondPage的ChildRoutes还是同一个函数,SecondPage重新渲染时,孙组件的路由组件不会被视为新组件,ThirdPage也就不会反复挂载了。

方法2:把路由配置设为组件静态属性

如果你的路由结构比较固定,可以把每个组件的子路由定义成它的静态属性,避免通过props传递函数:

// 给每个页面组件添加静态routes属性
MainPage.routes = [
  {
    path: '/main/second',
    component: SecondPage,
    routes: [
      { path: '/main/second/third', component: ThirdPage },
    ]
  }
];

SecondPage.routes = [
  { path: '/main/second/third', component: ThirdPage },
];

// 修改Routing组件,直接读取组件的静态routes
const Routing = ({ routes: passedRoutes, ...rest }) => {
  if (!passedRoutes) return null;
  return (
    <Switch>
      {passedRoutes.map(({ component: Component, ...route }) => {
        return (
          <Route key={route.path} {...route}>
            <Component {...rest} />
            {/* 直接渲染当前组件的子路由 */}
            <Routing routes={Component.routes} {...rest} />
          </Route>
        );
      })}
    </Switch>
  );
};

这种方式彻底避免了传递动态函数,自然不会有引用变化的问题,但缺点是路由结构的灵活性会降低,适合路由固定的场景。

总结

本质问题就是动态创建的函数引用不稳定,导致React误判组件类型,触发卸载重挂载。用useCallback缓存函数是最灵活的非Context方案,既能保留你原来的路由结构设计,又能解决重复挂载的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 14:12:41