Appearance
【Router】路由模式 ✨
要点速览
- 模式:
Hash(基于#,不发请求)、HTML5 History(无#,需服务器回退)、Memory(非浏览器环境)。 - 选择:面向浏览器与静态托管选
Hash;需美观 URL 与 SEO 配置选History;SSR/Node 环境选Memory。 - Vue 用法:
createWebHashHistory、createWebHistory、createMemoryHistory。 - 注意:
History刷新 404 需服务器回退到入口;BASE_URL影响资源与路由前缀。
动机与总览
前端路由的本质是在不刷新页面的情况下切换视图。不同模式以不同方式驱动 URL 与视图的对应关系:
Hash:依赖 URL 的#片段,变更不会向服务器发起请求。HTML5 History:使用 History API 更新地址栏,需后端配合回退路由。Memory:在非浏览器环境维护一份内存中的历史栈(如 SSR、Node)。
Hash 模式
Hash 是 URL 的组成部分,例如:
https://www.example.com/path?key=value#anchor其 # 后面的部分即为 Hash。早期常用于锚点导航
html
<a href="#target">go target</a>
...
<div id="target">i am target place</div>在上面代码中,点击 <a> 链接,文档会滚动到 id='target' 的 div 的位置。
Hash 另一个重要特性:Hash 的变化不会触发对服务器的请求。
利用该特性可以实现不同 URL 映射不同的模块。
实战:使用 Hash 实现单页应用
js
document.addEventListener("DOMContentLoaded", () => {
const content = document.getElementById("content");
const renderPage = (hash) => {
switch (hash) {
case "#home":
content.innerHTML = "<h1>Home</h1>";
break;
case "#about":
content.innerHTML = "<h1>About</h1>";
break;
case "#contact":
content.innerHTML = "<h1>Contact</h1>";
break;
default:
content.innerHTML = "<h1>404</h1>";
}
};
window.addEventListener("hashchange", () => {
renderPage(window.location.hash);
});
renderPage(window.location.hash || "#home");
});html
<body>
<nav>
<a href="#home">Home</a>
<a href="#about">About</a>
<a href="#contact">Contact</a>
</nav>
<div id="content"></div>
<script src="./index.js"></script>
</body>Vue-router 中如何使用 Hash 模式?
js
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [
/* ... */
],
});说明:import.meta.env.BASE_URL 为 Vite 的基础路径(开发默认 '/',生产可在 vite.config.ts 的 base 配置)。
HTML5 模式(History)
通过 History API 管理浏览器历史记录,实现“无 # 的美观 URL”:
history.pushState(state, title, url):将一个状态推入到历史堆栈里面history.replaceState(state, title, url):替换当前历史堆栈最上面的状态window.onpopstate:这是一个事件,当用户点击浏览器的前进或者后退按钮的时候,会触发该事件
History 实现原理
History 工作原理
- 拦截链接点击事件
- 客户端路由器会拦截页面上的所有链接点击事件(通常是通过阻止链接的默认行为
event.preventDefault()) - 取而代之的是,路由器使用 history.pushState 或 history.replaceState 更新 URL。
- 客户端路由器会拦截页面上的所有链接点击事件(通常是通过阻止链接的默认行为
- URL 变化处理:
- 当 URL 变化时,路由器会捕捉到这个变化。
- 路由器不会发出新的 HTTP 请求,而是根据新的 URL 查找预先定义好的路由规则,并加载相应的视图组件
举个例子,假设有一个单页应用,使用 history 模式,并且有以下路由规则:
- /home: 显示主页内容
- /about: 显示关于页面内容
当用户点击导航链接从 /home 切换到 /about 时,流程如下:
- 用户点击链接
<a href="/about">About</a> - 路由器拦截点击事件,调用
event.preventDefault()阻止浏览器的默认行为(即不发出 HTTP 请求) - 路由器调用
history.pushState(null, "", "/about")更新浏览器的地址栏 URL 为 /about - 路由器检测到 URL 变化,查找路由规则,发现 /about 对应的视图组件
- 路由器加载并渲染 /about 视图组件,将其插入到页面的特定位置
整个过程中,浏览器地址栏的 URL 更新了,但没有发出新的 HTTP 请求,所有的视图更新都是在客户端完成的。
实战:使用 History 实现单页应用
jsx
document.addEventListener("DOMContentLoaded", () => {
const content = document.getElementById("content");
const renderPage = (path) => {
console.log(path, "path");
switch (path) {
case "/":
case "/index.html":
content.innerHTML = "<h1>Home</h1>";
break;
case "/about":
content.innerHTML = "<h1>About</h1>";
break;
case "/contact":
content.innerHTML = "<h1>Contact</h1>";
break;
default:
content.innerHTML = "Not Found";
}
};
document.querySelectorAll("a[data-link]").forEach((link) => {
link.addEventListener("click", (e) => {
// 阻止默认事件
e.preventDefault();
const path = e.target.getAttribute("href");
// 推入到浏览器历史记录中
history.pushState(null, null, path);
renderPage(path);
});
});
renderPage(window.location.pathname || "/");
});html
// index.html
<body>
<nav>
<a href="/" data-link> Home </a>
<a href="/about" data-link> About </a>
<a href="/contact" data-link> Contact </a>
</nav>
<div id="content"></div>
<script src="./index.js"></script>
</body>Vue-router 中如何使用 History 模式?
js
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
// ...
],
});History 存在的问题
一旦刷新,就会报 404 错误。
思考 🤔 为什么会这样?
答案:当你刷新的时候,是会请求服务器的。但是服务器并没有这个后端路由,这个仅仅是一个前端路由。
要解决这个问题,需要在服务器上面做一些配置。添加一个回退路由,如果 URL 不匹配任何的静态资源,回退到首页。具体服务器配置可以参考官方文档
Memory 模式
无论是 Hash 也好、History API 也好,本质上都是基于浏览器的特性来实现的。
而 Memory 模式一般用于非浏览器环境,例如 Node 或者 SSR. 因为是非浏览器环境,所以不会有 URL 交互也不会自动触发初始导航。
该模式用 createMemoryHistory() 创建,并且需要在调用 app.use(router) 之后手动 push 到初始导航。
js
import { createRouter, createMemoryHistory } from "vue-router";
const router = createRouter({
history: createMemoryHistory(),
routes: [
/* ... */
],
});