一、传统路由:为什么点击链接会 "白屏"?
在早期的网页开发中,"页面跳转" 是通过<a>标签实现的。这种方式看似简单,却藏着影响用户体验的深层问题。
1.1 传统路由的工作流程
当你点击一个<a href="2.html">链接时,浏览器会执行一系列操作:
-
向服务器发送新页面的请求(如请求
2.html); -
等待服务器返回完整的 HTML 文件;
-
丢弃当前页面的所有内容,重新解析并渲染新 HTML。
这个过程中,从 "丢弃旧内容" 到 "渲染新内容" 的间隙,页面会呈现空白 —— 这就是 "白屏" 的由来。
示例代码解析 :
<!-- 导航 -->
<nav>
<ol>
<li><a href="./1.html">Page 1</a></li>
<li><a href="./2.html">Page 2</a></li>
</ol>
</nav>
这里的<a>标签本质是 "全页替换":每次点击都会让浏览器重新加载整个页面,导致状态丢失、等待时间长、白屏频繁。
1.2 核心痛点:用户体验的 "绊脚石"
- 性能浪费:重复加载公共部分(如导航栏、页脚),明明不需要重新渲染,却被强制刷新;
- 白屏中断:网络较慢时,白屏时间可长达几秒,用户会误以为页面崩溃;
- 状态丢失:跳转后页面的滚动位置、输入框内容等状态全部重置。
二、前端路由:SPA 如何实现 "URL 变而页面不刷"?
为解决传统路由的问题,"前端路由" 应运而生,其核心载体是单页应用(SPA) 。
2.1 什么是 SPA?
SPA(Single Page Application)即 "单页应用",指整个应用只有一个 HTML 页面,但能通过前端技术模拟多页面切换效果。
- 核心逻辑:用 "组件替换" 代替 "全页刷新";
- 关键优势:URL 变化时,只更新页面中需要变化的部分(如内容区),导航栏、页脚等公共部分保持不变。
2.2 前端路由的 3 个核心目标
- URL 必须变:用户需要看到 URL 变化(方便收藏、回退);
- 页面不能刷:禁止浏览器发送新请求、重新渲染整个页面;
- 内容动态换:根据 URL 自动切换对应的页面组件(如 / Home→Home 组件,/About→About 组件)。
2.3 实现秘诀:URL 变化不刷新的 2 种方案
URL 变化却不触发刷新,靠的是前端对浏览器 API 的 "巧思利用":
| 方案 | 原理 | 示例 URL | 事件监听 |
|---|---|---|---|
| hash 模式 | 利用 URL 中#后的哈希值变化 | http://xxx.com#/home | hashchange |
| history 模式 | 利用 HTML5 的pushStateAPI | http://xxx.com/home | 无原生事件(需手动监听) |
三、实战:用原生 JS 手写 hash 模式路由
hash 模式是前端路由的 "元老级" 实现,借助 URL 中的#(哈希)特性:#后的内容变化不会触发页面刷新,且会触发hashchange事件。
3.1 核心步骤
- 用
<a href="#home">代替<a href="home.html">,通过哈希值标识页面; - 监听
hashchange事件,捕捉 URL 中哈希的变化; - 根据当前哈希值,动态更新页面内容区(替换组件)。
3.2 代码逐行解析
<!-- 导航:用#标识不同页面 -->
<ol>
<li><a href="#home">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#contact">Contact</a></li>
</ol>
<!-- 内容区:需要动态更新的部分 -->
<div id="content-container">
<h2>Hello</h2>
<p>Welcome to our SPA!</p>
</div>
<script>
// 获取内容区DOM
const content = document.getElementById('content-container');
// 监听hash变化:当#后的内容改变时触发
window.addEventListener('hashchange', () => {
// 根据当前哈希值切换内容
switch (window.location.hash) {
case '#home':
// 替换内容区为Home页面
content.innerHTML = '<h2>Home</h2><p>Welcome to homepage</p>';
break;
case '#about':
content.innerHTML = '<h2>About</h2><p>We are a company</p>';
break;
case '#contact':
content.innerHTML = '<h2>Contact</h2><p>Contact us</p>';
break;
}
})
</script>
效果:点击导航时,URL 中#后的内容变化(如#home→#about),内容区动态更新,页面全程不刷新、不白屏。
四、进阶:react-router-dom 玩转 SPA
在 React 项目中,react-router-dom是处理 SPA 路由的 "利器",封装了 hash/history 模式的复杂逻辑,提供简洁的组件 API。
4.1 核心组件解析
| 组件名 | 作用 | 替代对象 |
|---|---|---|
Router | 包裹整个路由系统(选择 hash/history 模式) | 无(路由的 "容器") |
Link | 实现 URL 变化(不刷新页面) | <a>标签 |
Routes | 路由规则的容器 | 无(类似switch) |
Route | 定义 "URL→组件" 的映射规则 | 无(类似case) |
4.2 代码实战
// 引入路由核心组件
import {
BrowserRouter as Router,
Routes,
Route,
Link
} from 'react-router-dom'
// 引入页面组件
import Home from './pages/Home'
import About from './pages/About'
function App() {
return (
<Router> {/* 路由容器:所有路由相关组件必须在其内部 */}
{/* 导航:用Link代替a标签,避免刷新 */}
<nav>
<ol>
<li><Link to="/">Home</Link></li> {/* to="/" 对应首页 */}
<li><Link to="/about">About</Link></li> {/* to="/about" 对应关于页 */}
</ol>
</nav>
{/* 路由规则:URL匹配时显示对应的组件 */}
<Routes>
{/* path="/" 匹配根路径,显示Home组件 */}
<Route path="/" element={<Home />} />
{/* path="/about" 匹配/about路径,显示About组件 */}
<Route path="/about" element={<About />} />
</Routes>
</Router>
)
}
关键细节:
Link的to属性会通过 JS 修改 URL(history 模式用pushState,hash 模式用哈希),不会触发默认跳转;Routes会根据当前 URL,只渲染匹配的Route组件(类似 "单选");- 公共部分(如
<nav>)在Routes外部,不会随页面切换更新。
五、总结:路由进化的本质
从传统路由到前端路由,核心变化是 "页面跳转"→"组件切换":
-
传统路由:依赖后端返回完整 HTML,全页刷新导致体验差;
-
前端路由:靠 JS 控制 URL 变化 + 组件动态替换,实现 "无刷新切换",SPA 由此诞生。
无论是原生 JS 手写 hash 路由,还是用react-router-dom快速开发,核心目标始终一致:让用户在 URL 变化时,感受到 "无缝切换" 的流畅体验。