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,在刷新之前打开开发者工具,依次检查:
- 确认地址栏已经变成
/expertise。 - 在 Elements 面板搜索
Here are some of my expertise。 - 如果找到对应节点,检查它及其祖先元素的 Computed 样式,尤其是
opacity、visibility、display和transform。 - 临时删除相关元素上的
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 或强制刷新页面。
备注:内容仅供参考。
