引言
随着网站越来越复杂,很多网页已经不再像以前那样,每点击一个链接就重新加载整个页面。过去的“多页应用”(MPA)就像翻书,每翻一页都要把整本书合上再打开,非常慢。而现在流行的“单页应用”(SPA)更像是一本活页夹,只换里面的内容,外壳不动,速度更快,体验更好。
在这种新模式下,前端路由技术就像是“导航员”,负责根据用户的操作,决定显示哪一部分内容。路由不仅影响页面切换的流畅度,还会影响网站能不能被搜索引擎收录、能不能兼容不同的浏览器,以及能不能和服务器配合实现更快的首屏加载。所以,前端路由技术是现代 Web 开发中非常重要的一环。
传统多页路由的痛点
传统的多页应用(MPA)其实就是每个页面都是一个单独的 HTML 文件。用户每点击一次跳转链接,浏览器就会向服务器请求一个新的页面,服务器把完整的页面内容发回来,浏览器再重新加载和渲染。
这种方式有几个明显的缺点:
- 速度慢,体验差:每次跳转都要重新下载页面、样式和脚本,页面会闪一下,用户感觉不流畅。
- 数据和状态容易丢:页面一刷新,之前 JS 里保存的数据就没了。如果想保存一些信息,只能用 Cookie 或 LocalStorage 这些方式,比较麻烦。
- 开发起来不方便:每个页面都是独立的,想要复用一些功能或者样式很难,维护起来工作量大。
- 前后端分工不清晰:页面的内容和跳转都要后端来处理,前端和后端需要紧密配合,开发流程复杂。
在发现传统多页应用的不足后,开发者们开始尝试让页面切换变得更流畅。
最早被广泛采用的方案就是 Hash 路由。
Hash 路由的原理
Hash 路由利用了浏览器地址栏中的 #(哈希)符号。# 后面的内容不会被浏览器当作真正的路径去请求服务器,而是只在本地变化。这样,页面不会刷新,但地址栏会变化,用户也能用浏览器的前进、后退按钮。
来看一个简单的实现:
// 监听 hash 变化事件
window.addEventListener('hashchange', () => {
// 获取 # 后面的路由路径
const route = location.hash.slice(1);
// 根据不同的路由,显示不同的内容
if (route === '/home') {
document.body.innerHTML = '<h1>首页</h1>';
} else if (route === '/about') {
document.body.innerHTML = '<h1>关于我们</h1>';
} else {
document.body.innerHTML = '<h1>欢迎访问</h1>';
}
});
// 初始化时也要根据 hash 显示内容
window.dispatchEvent(new Event('hashchange'));
这里通过监听 hashchange 事件实现前端路由:
- 监听变化:当 URL 中
#后的哈希值改变,触发回调。 - 处理路由:截取哈希路径,根据不同路径,用
innerHTML替换body内容,显示对应标题。 - 初始化执行:页面加载时,手动触发
hashchange事件,确保根据初始哈希显示对应内容 。
Hash 路由的优缺点
Hash 路由的出现,让页面切换变得流畅了许多,但它也有一些局限:
优点:
- 实现简单,不需要服务器配合,前端即可完成路由切换。
- 兼容性好,几乎所有浏览器都支持
hashchange事件。 - 页面刷新不会出现 404,因为 # 后内容不会被服务器解析。
缺点:
- URL 中会出现 #,不够美观,也不利于搜索引擎收录。
- 只能做前端页面切换,无法和后端路由统一,无法实现服务端渲染。
- 功能有限,复杂场景下维护和扩展不方便。
History API:更自然的前端路由体验
随着前端需求的提升,开发者希望地址栏能像传统网站一样,显示“正常”的路径(如 /about),而不是带有 # 的路径。此时,HTML5 新增的 History API 成为更好的选择。
History API 的基本用法
History API 允许前端代码直接修改浏览器的历史记录和地址栏路径,但不会刷新页面。这样,用户看到的 URL 更加自然,体验也更接近传统网站。
// 切换到 /about 路由,但不刷新页面
history.pushState({}, '', '/about');
// 监听浏览器的前进/后退操作
window.addEventListener('popstate', () => {
// 获取当前路径
const path = location.pathname;
// 根据路径显示不同内容
if (path === '/home') {
document.body.innerHTML = '<h1>首页</h1>';
} else if (path === '/about') {
document.body.innerHTML = '<h1>关于我们</h1>';
} else {
document.body.innerHTML = '<h1>欢迎访问</h1>';
}
});
在这里利用 HTML5 History API 实现前端路由:
- 无刷新改路由:
history.pushState可在不刷新页面时,修改 URL 路径(如设为/about),并新增历史记录 。 - 监听前进后退:通过
window.addEventListener('popstate'),捕获浏览器前进/后退操作 。 - 动态更新内容:事件触发时,依据
location.pathname获取当前路径,匹配不同路径(/home、/about等),用innerHTML替换body内容,实现单页应用路由切换效果 。
History 路由的部署注意事项
使用 History 路由时,用户如果直接访问某个路径(如 /about),浏览器会向服务器请求 /about,如果服务器没有做特殊处理,就会返回 404。为了解决这个问题,需要让服务器无论访问哪个路径,都返回同一个入口页面(通常是 index.html)。
Nginx 配置示例
server {
listen 80;
server_name example.com;
root /usr/share/nginx/html;
location / {
# 如果找不到对应文件,就返回 index.html
try_files $uri $uri/ /index.html;
}
}
这段 Nginx 配置的核心是:
- 让 Nginx 正确托管静态文件(通过
root指定目录 )。 - 通过
try_files,确保单页应用刷新时,无论访问什么路径,最终都返回index.html,让前端路由能正常工作,解决刷新 404 问题。
Node(Express)配置示例
const express = require('express');
const path = require('path');
const app = express();
// 静态资源托管
app.use(express.static('public'));
// 所有其他路由都返回 index.html
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'public', 'index.html'));
});
app.listen(3000);
用 Express 搭建服务部署单页应用配置:
- 初始化:引入
express和path模块,创建 Express 应用app。 - 托管静态资源:用
express.static将public目录设为静态资源目录,直接响应 JS、CSS 等文件请求 。 - 处理路由:通过
app.get('*')捕获所有未匹配路由,用sendFile返回public目录下的index.html,适配前端路由,避免刷新 404 。 - 启动服务:监听 3000 端口,让应用可通过该端口访问 ,实现单页应用的服务端支持与路由兼容。
History 路由的优缺点
优点:
- URL 更加美观,和传统网站一致,用户体验更好。
- 支持 SEO 和服务端渲染(SSR),有利于搜索引擎收录。
- 可以和后端路由统一,便于前后端协作。
缺点:
- 需要服务器配合,配置不当会导致刷新页面 404。
- 低版本浏览器(IE9 以下)不支持。
- 部署和调试时需要注意路径和资源的处理。
从原生到工程化:React Router 的出现
随着前端项目越来越复杂,手写路由逻辑变得繁琐且难以维护。为了解决这些问题,像 React Router 这样的专业路由库应运而生。
原生路由与 React Router 的对比
在原生实现中,开发者需要手动监听事件、解析路径、渲染内容,代码容易混乱。而 React Router 提供了声明式的路由配置和组件化的页面管理方式,大大简化了开发流程。
原生实现示例: 下面是一个简单的原生前端路由实现(以 Hash 路由为例)
// 定义路由和对应的渲染函数
const routes = {
'/home': () => {
document.body.innerHTML = '<h1>首页</h1>';
},
'/about': () => {
document.body.innerHTML = '<h1>关于我们</h1>';
},
'/': () => {
document.body.innerHTML = '<h1>欢迎访问</h1>';
}
};
// 路由切换处理函数
function onRouteChange() {
// 获取当前 hash 路径
const path = location.hash.slice(1) || '/';
// 查找对应的渲染函数并执行
const render = routes[path];
if (render) {
render();
} else {
document.body.innerHTML = '<h1>页面未找到</h1>';
}
}
// 监听 hash 变化
window.addEventListener('hashchange', onRouteChange);
// 页面首次加载时也要执行一次
window.addEventListener('DOMContentLoaded', onRouteChange);
核心逻辑可总结为:
1. 路由配置
定义对象 routes,以路由路径(如/home、/about )为键,对应的值是渲染函数,函数内通过修改 document.body.innerHTML 来更新页面内容,实现不同路由展示不同标题 。
2. 路由切换处理
-
onRouteChange函数:- 先从
location.hash中截取哈希路径(去掉#),若为空则默认设为/。 - 根据路径到
routes中找对应的渲染函数,找到就执行渲染;找不到则渲染 “页面未找到” 提示,以此实现路由变化时页面内容的动态更新 。后续若结合hashchange事件监听(代码里未完整体现,实际用需补充 ),就能响应 URL 哈希变化,完成前端路由切换。
- 先从
React Router 实现示例:
import { BrowserRouter, Routes, Route } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/home" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
关键逻辑:
1. 路由基础依赖
从 react-router-dom 引入 3 个核心组件:
BrowserRouter:包裹整个应用,启用 HTML5 History 模式路由(URL 用真实路径,如/home,需后端配合支持 )。Routes:路由容器,负责匹配和渲染子Route组件。Route:定义单个路由规则(路径 + 对应组件 )。
2. 路由规则定义
在 App 组件中,通过嵌套组件配置路由:
path="/home":访问/home路径时,渲染<HomePage />组件。path="/about":访问/about路径时,渲染<AboutPage />组件。path="*":特殊通配符,匹配所有未定义的路径(404 场景),渲染<NotFound />组件。
小结
前端路由让网页从“整页刷新”进化到“局部更新”,先后经历了 Hash、History API 再到 React Router 三个阶段的跃迁。
- Hash 路由用
#实现无刷新跳转,简单兼容,但 URL 不美观、不利于 SEO。 - History API 让地址栏回归自然路径,体验更像传统网站,却需要服务器兜底防 404。
- React Router 等工程化方案则把这些底层细节封装成声明式组件,开发者只需描述“路径对应界面”,就能获得嵌套路由、懒加载、权限控制等高级能力,同时兼顾 SEO 和服务端渲染需求。
一句话:前端路由是 SPA 的“导航系统”,从原生事件监听走向组件化配置,既改善了用户体验,也重塑了前后端协作模式,已成为现代 Web 开发的标配技术。