PAYATHON 2026

ReactJS 中使用 React Router 切换路由后,为什么必须刷新页面才能显示组件?

排障赵师傅

结论

React Router 正常切换路由时不需要刷新页面。按 React Router v5 的 API,你提供的 Link、Switch 和 Route 配置可以完成客户端导航,没有明显需要刷新才能生效的问题。

建议先检查组件中多次出现的 animate-box。目标组件可能已经渲染到 DOM 中,只是被动画样式隐藏了。如果模板的动画脚本只在整页加载时初始化,点击 Link 后新挂载的元素就可能一直不可见,直到刷新后脚本重新初始化。

目前缺少相关 CSS 和动画初始化脚本,还不能确认原因,可以先沿这个方向排查。

为什么刷新后会显示?

点击 Link 时,BrowserRouter 会更新地址并渲染匹配路由的组件,浏览器不会重新加载整个文档。因此,放在 DOMContentLoaded、window.load 或 jQuery ready 回调中的初始化代码,不会随路由切换再次执行。

假设模板中有这样的样式:

/* 示意代码,具体规则需要检查项目中的 CSS */
.animate-box {
  opacity: 0;
}

另有一段脚本在元素进入视口时添加动画类,让它恢复可见。如果脚本只扫描了首次加载时的元素,后来由 React 挂载的 WhatIDo、Timeline 内容就不会得到处理。

这种情况下,控制台也可能没有报错。opacity: 0 是合法样式,元素完全透明不会触发 JavaScript 异常。

第一步:确认组件是否已经渲染

点击 What I Do,在刷新之前打开开发者工具,依次检查:

  1. 确认地址栏已经变成 /expertise。
  2. 在 Elements 面板搜索 Here are some of my expertise。
  3. 如果找到对应节点,检查它及其祖先元素的 Computed 样式,尤其是 opacity、visibility、display 和 transform。
  4. 临时删除相关元素上的 animate-box,或者取消隐藏它的 CSS 规则。

如果取消这些样式后内容立即出现,就能确认路由已经完成组件渲染,问题出在显示或动画逻辑上。

检查时要包括祖先元素:父元素的 opacity: 0 会让整块内容透明,只修改内部文字的样式没有用。

如果 DOM 中没有目标内容,再检查路由匹配、组件运行时异常,以及实际安装的 React Router 版本。

第二步:不需要入场动画时,移除 animate-box

保留布局类,去掉依赖外部脚本控制显示的动画标记即可。

例如,WhatIDo 的标题容器可以改为:

<div className="col-md-6 col-md-offset-3 col-md-pull-3">
  <span className="heading-meta">What I do?</span>
  <h2 className="colorlib-heading">
    Here are some of my expertise
  </h2>
</div>

各个服务卡片的外层也去掉 animate-box:

<div className="col-md-4 text-center">
  <div className="services color-1">
    {/* 保留原来的卡片内容 */}
  </div>
</div>

Timeline 中的标题、每一条经历和最后的时间线起点也需要处理:

- className="col-md-6 col-md-offset-3 col-md-pull-3 animate-box"
+ className="col-md-6 col-md-offset-3 col-md-pull-3"

- <h2 className="colorlib-heading animate-box">Timeline</h2>
+ <h2 className="colorlib-heading">Timeline</h2>

- className="timeline-entry animate-box"
+ className="timeline-entry"

- className="timeline-entry begin animate-box"
+ className="timeline-entry begin"

如果已经没有脚本使用相应的 data-animate-effect,也可以一起删除。这个属性本身不会产生动画,也不会自动让元素显示。

修改后,不刷新页面,连续切换几个菜单,再检查浏览器的前进、后退是否正常。

第三步:需要保留动画时,让动画跟随组件挂载

如果只需要简单的入场效果,可以使用组件自己的 CSS 动画,减少对模板初始化脚本的依赖。

例如,将 WhatIDo 的根元素改为:

<section
  className="colorlib-about route-enter"
  data-section="whatido"
>
  {/* 保留原来的内容,移除内部依赖旧脚本的 animate-box */}
</section>

在 CSS 中定义动画:

.route-enter {
  animation: portfolio-route-enter 300ms ease-out;
}

@keyframes portfolio-route-enter {
  from {
    opacity: 0;
    transform: translateY(12px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .route-enter {
    animation: none;
  }
}

路由切换使组件重新挂载时,元素插入页面就会触发动画。由于没有把元素的默认状态设为透明,动画不执行时,内容也能正常显示。

如果要保留原来的滚动动画插件,应在组件挂载后初始化当前组件中的元素,并在卸载时清理监听器或观察器。具体调用方式取决于插件,目前缺少插件代码,无法确定该调用哪个初始化函数。每次切换路由时重复执行整个模板脚本,可能会累积事件监听器,应避免这种做法。

路由配置还有哪些地方需要注意?

render 和 component 都可以使用

按 React Router v5 的写法,下面的配置可以保留:

<Switch>
  <Route
    path="/about"
    render={(props) => <About {...props} summary={summary} />}
  />
  <Route path="/expertise" render={() => <WhatIDo />} />
  <Route path="/experience" component={Timeline} />
</Switch>

render={() => <WhatIDo />} 即使没有传递路由 props,也能正常渲染;component={Timeline} 同样有效。目前没有证据表明,切换这两种写法能解决问题。

实际使用的 API 应以项目依赖版本为准。这里的配置适用于 v5;v6 及后续版本使用 Routes 和 element,这些路由声明方式不能混用。

Introduction 当前会一直显示

Introduction 位于 Switch 外部,会出现在所有路由中。点击 Home 回到 / 时,现有 Switch 没有匹配项,页面仍会显示外部的 Introduction 和其他公共内容。

如果希望介绍组件只出现在首页,可以将它移入 Switch,使用:

<Route
  exact
  path="/"
  render={() => (
    <Introduction myLinks={myLinks} email={email} />
  )}
/>

菜单仍应保留在 Switch 外。这个调整影响页面布局,需要与动画导致的不可见问题分别处理。

数据请求和服务器回退不是当前的首要线索

App 中依赖数组为 [] 的 useEffect 不会因为普通路由切换而重新请求数据。请求成功后的状态更新,仍会将新的 summary 传给 About。你提供的 WhatIDo 和 Timeline 都不依赖这次请求,因此请求未完成无法解释这两个组件都不显示的现象。

服务器没有配置单页应用回退时,常见表现是站内点击正常,直接访问或刷新子路由却返回 404。你描述的情况是刷新后能够显示,建议先检查 DOM 和动画样式,暂时不必改用 HashRouter 或强制刷新页面。

备注:内容仅供参考。