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

为什么react-router Route的render属性触发组件重挂载而非重渲染?

问题根因

  1. 列表渲染缺少稳定唯一key:你在MySwitch的Route render方法最后对缓存路由做列表渲染时,没有给每个外层div添加key属性。React Diff列表类子元素时,如果没有稳定key会默认按位置比对,上层组件重渲染触发render重新执行时,会判定所有列表元素为新节点,直接销毁旧节点重新挂载,导致缓存组件每次都被重新初始化。
  2. 全局缓存变量污染:你把cacheRouteMap声明为组件外的全局变量,会出现多MySwitch实例缓存冲突、组件卸载后缓存无法自动清理的问题,也可能引发异常挂载。
  3. 路由组件定义位置错误:如果你的路由组件(比如测试代码里的Demo)定义在父组件内部,父组件重渲染时会生成新的组件引用,React会判定为不同类型组件,直接触发重挂载。
  4. Route props 写法错误:你原App代码里给Route传的component属性值是JSX元素,不符合React Router v5的规范,component属性应该接收组件构造函数,传JSX会导致每次渲染生成新元素,触发重挂载。

修改方案

1. 修正MySwitch实现

// MySwitch
import React from 'react'
import { Route, matchPath } from 'react-router-dom'

const MySwitch = props => {
  // 缓存放入组件内部ref,避免全局污染
  const cacheRouteMap = React.useRef({})

  return (
    <Route 
      path="*" 
      render={context => {
        const location = props.location || context.location
        let element
        let match
        let currentMatchPath
        
        React.Children.forEach(props.children, child => {
          // eslint-disable-next-line no-eq-null
          if (match == null && React.isValidElement(child)) {
            element = child
            const path = child.props.path || child.props.from
            currentMatchPath = path
            match = path ? matchPath(location.pathname, {...child.props, path}) : context.match
          }
        })

        if (currentMatchPath) {
          if (!cacheRouteMap.current[currentMatchPath]) {
            // 首次匹配缓存组件
            cacheRouteMap.current[currentMatchPath] = React.cloneElement(element, {
              location,
              computedMatch: match,
            })
          } else {
            // 已缓存的组件更新路由相关props,避免拿到旧的路由数据
            cacheRouteMap.current[currentMatchPath] = React.cloneElement(cacheRouteMap.current[currentMatchPath], {
              location,
              computedMatch: match,
            })
          }
        }

        // 给每个列表项添加唯一key,值为路由路径
        return Object.entries(cacheRouteMap.current).map(([path, cachedElement]) => {
          return (
            <div 
              key={path} 
              style={{display: path === currentMatchPath ? 'block' : 'none'}}
            >
              {cachedElement}
            </div>
          )
        })
      }} 
    />
  )
}

2. 修正路由写法

App里的Route组件不要给component传JSX元素,两种正确写法二选一:

// 写法1:用component传组件构造函数
<Route path="path1" component={Component1}>
// 写法2:用render返回JSX
<Route path="path1" render={() => <Component1 />}>

3. 修正组件定义位置

所有路由组件要定义在父组件外部,不要嵌套在其他组件的函数体内。如果必须在render函数内返回组件,可以用useCallback包裹render回调,避免每次生成新的函数引用:

const App = () => {
  const [visible, setVisible] = useState(false)
  // 用useCallback缓存render回调
  const renderDemo = React.useCallback(() => <Demo />, [])

  return (
    <div>
      <div onClick={() => setVisible(!visible)}>button</div>
      <div style={{display: visible ? 'block' : 'none'}}>xxxxx</div>
      <Router>
        <Switch>
          <Route path="/" render={renderDemo} />
          <Redirect to="/" />
        </Switch>
      </Router>
    </div>
  )
}

修改完成后上层组件重渲染时,缓存的路由组件只会触发重渲染,不会重新挂载。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.20 21:27:02