一、 何为前端路由?为何需要它?
在传统网站中,路由是由服务器端控制的。当用户点击一个链接(如/about
),浏览器会向服务器发送一个请求,服务器根据接收到的URL路径返回对应的HTML文档。这个过程会导致整个页面刷新。
而在SPA中,整个应用通常只有一个index.html
。如果依然依赖服务器根据路径返回不同页面,就失去了SPA的意义。因此,路由的控制权必须从服务器转移到浏览器(客户端)。这就是前端路由的职责:
- 映射规则:建立URL路径(Path)与UI组件(View)之间的映射关系。
- 监听变化:监听浏览器URL的变化。
- 执行回调:当URL变化时,阻止浏览器默认的请求行为,转而执行一个JavaScript回调函数。
- 更新视图:该回调函数会根据当前URL,找到匹配的组件,并将其渲染到页面指定的容器中。
二、 Hash模式
Hash模式是前端路由最早实现也是兼容性最好的方案。
Hash指的是URL中井号(#
)及后面的部分,例如 https://example.com/#/about
。Hash有以下几个关键特性,使其成为实现路由的理想选择:
- 不会触发页面刷新 :改变URL的Hash部分(无论是通过
location.hash
赋值、手动修改还是通过<a>
标签的href
属性),浏览器只会改变历史记录,而不会向服务器发送新的请求。 - 用于页面锚点定位:Hash原本的设计目的是用于页面内的锚点跳转。
- 可被监听 :浏览器提供了
hashchange
事件,专门用于监听Hash的变化。
基于这些特性,Hash 模式的路由实现逻辑可总结为三步:
- 初始化路由映射:定义 URL Hash 值与页面视图(组件)的对应关系,如#/home对应 "首页" 组件,#/about对应 "关于页" 组件;
- 监听 Hash 变化:通过window.addEventListener('hashchange', handler)监听 Hash 值变化,同时在页面首次加载时(load事件)处理初始 Hash 值;
- 匹配路由并更新视图:当 Hash 值变化时,解析当前 Hash 值,匹配对应的视图组件,通过 DOM 操作动态渲染组件内容(如隐藏其他组件、显示目标组件)。
三、 History模式
随着HTML5标准的推出,History API新增了pushState()
和replaceState()
方法,赋予了开发者直接操作浏览器会话历史记录的能力,从而催生了更优雅的History路由模式。
1. 原理剖析
History模式的核心是利用以下两个API:
window.history.pushState(state, title, url)
markdown
向历史记录栈中**压入**一个新的状态。
`state`: 一个与新历史记录条目关联的状态对象,可以在`popstate`事件中获取。
`title`: 目前大多数浏览器会忽略这个参数,可以传空字符串。
`url`: **新的URL**。这个URL必须与当前页面同源,否则会抛出错误。
注意:调用此方法后,浏览器URL栏会改变,但浏览器不会加载这个URL,甚至不会检查该URL是否存在。
window.history.replaceState(state, title, url)
markdown
用法与`pushState`类似,区别在于它不是新增一条历史记录,而是**替换**当前的历史记录。
如何监听变化?
当用户点击浏览器的前进 、后退 按钮时,或者开发者调用history.back()
, history.forward()
, history.go()
方法时,会触发popstate
事件。我们可以通过监听这个事件来响应URL的变化。
重要提示 :直接调用pushState()
或replaceState()
不会 触发popstate
事件。
2. 服务器端配置:History模式的关键陷阱
History模式最大的挑战在于服务器端配置。
假设你有一个SPA,它的路由是/about
。这个路由是由前端定义的。
- 开发环境 :通常没问题,因为Vue CLI或Webpack Dev Server已经为你配置了
historyApiFallback
,它会将所有404请求重定向到index.html
。 - 生产环境 :当你直接在浏览器地址栏输入
https://yourdomain.com/about
并回车时,浏览器会向服务器发送一个对/about
的真实HTTP请求。
如果你的服务器(如Nginx、Apache)没有进行特殊配置,它会尝试在服务器根目录下寻找about
这个文件或目录。显然,它找不到,于是返回404错误。
解决方案 :你必须在服务器端配置一个"回退"规则,即当请求的资源不存在时,不是返回404,而是统一返回SPA的入口文件index.html
,让前端路由自己去处理这个路径。
(1)Nginx 配置
修改nginx.conf或站点配置文件,添加try_files指令:
js
server {
listen 80;
server_name example.com; # 你的域名
root /usr/share/nginx/html; # 项目根目录(index.html所在目录)
index index.html; # 默认入口文件
# 关键配置:所有请求都指向index.html
location / {
try_files $uri $uri/ /index.html;
}
}
- try_files <math xmlns="http://www.w3.org/1998/Math/MathML"> u r i uri </math>uriuri/ /index.html:Nginx 会优先尝试访问请求的文件( <math xmlns="http://www.w3.org/1998/Math/MathML"> u r i )或目录( uri)或目录( </math>uri)或目录(uri/),若不存在则返回/index.html。
(2)Apache 配置
在项目根目录创建.htaccess文件(需开启mod_rewrite模块):
js
<IfModule mod_rewrite.c>
RewriteEngine On # 开启重写引擎
RewriteBase / # 重写基准路径
RewriteRule ^index.html$ - [L] # 直接访问index.html时不重写
RewriteCond %{REQUEST_FILENAME} !-f # 若请求的不是文件
RewriteCond %{REQUEST_FILENAME} !-d # 若请求的不是目录
RewriteRule . /index.html [L] # 重写到index.html
</IfModule>
(3)Node.js(Express)配置
使用express框架时,通过中间件处理所有路由:
js
const express = require('express');
const path = require('path');
const app = express();
const port = 3000;
// 静态文件托管(CSS、JS、图片等)
app.use(express.static(path.join(__dirname, 'dist'))); // dist为项目打包目录
// 所有路由请求都返回index.html
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
});
// 启动服务器
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
四、总结
原理
-
Hash 模式 :利用 URL 中
#
后的哈希片段实现路由。哈希变化不会触发页面刷新,但会触发hashchange
事件,通过监听该事件动态更新页面内容。 -
History API 模式 :基于 HTML5 的
history.pushState()
和history.replaceState()
方法,直接修改 URL 路径而不刷新页面,通过监听popstate
事件处理前进/后退操作。
相同点
-
均用于实现单页面应用(SPA)的路由控制,避免整页刷新。
-
支持浏览器历史记录管理,可通过前进/后退导航。
-
需通过 JavaScript 动态渲染页面内容。
不同点
-
URL 美观性 :Hash 模式含
#
,不直观;History 模式与常规 URL 无异。 -
兼容性:Hash 模式兼容所有浏览器(包括 IE6+);History 模式需 IE10+ 及现代浏览器。
-
服务器配置 :Hash 模式无需服务器支持;History 模式需服务器配置(如 Nginx 重定向到
index.html
),否则直接访问子路由会返回 404。 -
SEO 友好性:Hash 模式对搜索引擎不友好(依赖 Google 等爬虫对哈希内容的解析);History 模式可通过 SSR 或预渲染优化 SEO。
-
实现复杂度:Hash 模式简单易用;History 模式需处理服务器配置和动态路由匹配,复杂度较高。