前端路由原理
前端路由的核心,就在于改变视图的同时不会向后端发出请求;而是加载路由对应的组件。vue-router就是将组件映射到路由, 然后渲染出来的。并实现了三种模式:Hash模式、History模式以及Abstract模式。默认Hash模式,今天主要介绍Hash模式和History模式。
Hash模式
- 原理
基于浏览器的hashchange事件,地址变化时,通过window.location.hash 获取地址上的hash值;并通过构造Router类,配置routes对象设置hash值与对应的组件内容。 - 优点
- hash值会出现在URL中, 但是不会被包含在Http请求中, 因此hash值改变不会重新加载页面
- hash改变会触发hashchange事件, 能控制浏览器的前进后退
- 兼容性好
- 缺点
- 地址栏中携带#,不美观
- 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URL
- hash有体积限制,故只可添加短字符串
- 设置的新值必须与原来不一样才会触发hashchange事件,并将记录添加到栈中
- 每次URL的改变不属于一次http请求,所以不利于SEO优化
- 代码实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>hash</title>
</head>
<body>
<div class="main">
<a href="#/a">a页面</a>
<a href="#/b">b页面</a>
<a href="#/c">c页面</a>
<div id="content"></div>
</div>
<script>
// Router 构造函数
class Router {
constructor(routers) {
this.routers = {};
routers.forEach((item) => {
// 每个hash值 匹配对应component
this.route(item.path, () => {
document.getElementById("content").innerHTML = item.compontent;
});
});
this.init();
}
route(path, cb) {
this.routers[path] = cb;
}
init() {
window.addEventListener("load", this.updateView.bind(this));
// hash模式 路由修改时 浏览器会触发hashchange事件 调用更新视图函数
window.addEventListener("hashchange", this.updateView.bind(this));
}
updateView(e) {
// console.log("hash window.location", window.location);
// 获取页面hash值 通过hash值更新对应的组件内容
const hashTag = window.location.hash.slice(1) || "/";
this.routers[hashTag] && this.routers[hashTag]();
}
}
const routers = [
{
path: "/a",
compontent: `<div>我是a页面</div>`,
},
{
path: "/b",
compontent: `<div>我是b页面</div>`,
},
{
path: "/c",
compontent: `<div>我是c页面</div>`,
},
];
new Router(routers);
</script>
</body>
</html>
History模式
-
原理
基于HTML5新增的pushState()和replaceState()两个api,以及浏览器的popstate事件,地址变化时,通过window.location.pathname找到对应的组件。并通过构造Router类,配置routes对象设置pathname值与对应的组件内容。 -
优点
- 没有#,更加美观
- pushState() 设置的新 URL 可以是与当前 URL 同源的任意 URL
- pushState() 设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中
- pushState() 通过 stateObject 参数可以添加任意类型的数据到记录中
- pushState() 可额外设置 title 属性供后续使用
- 浏览器的进后退能触发浏览器的popstate事件,获取window.location.pathname来控制页面的变化
- 缺点
- URL的改变属于http请求,借助history.pushState实现页面的无刷新跳转,因此会重新请求服务器。所以前端的 URL 必须和实际向后端发起请求的 URL 一致。如果用户输入的URL回车或者浏览器刷新或者分享出去某个页面路径,用户点击后,URL与后端配置的页面请求URL不一致,则匹配不到任何静态资源,就会返回404页面。所以需要后台配置支持,覆盖所有情况的候选资源,如果 URL 匹配不到任何静态资源,则应该返回app 依赖的页面或者应用首页。
- 兼容性差,特定浏览器支持
- 代码实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>history</title>
</head>
<body>
<div class="main">
<a href="javascript:;" path="/a">a页面</a>
<a href="javascript:;" path="/b">b页面</a>
<a href="javascript:;" path="/c">c页面</a>
<div id="content"></div>
</div>
<script>
class Router {
constructor(routers) {
this.routers = {};
routers.forEach((item) => {
this.route(item.path, () => {
document.getElementById("content").innerHTML = item.compontent;
});
});
this.bindClick();
this.init();
}
route(path, cb) {
this.routers[path] = cb;
}
bindClick() {
// history模式需要手动添加路由 通过 history的pushState事件
const links = document.getElementsByTagName("a");
// [].forEach.call() => Array.prototype.forEach()
[].forEach.call(links, (link) => {
link.addEventListener("click", () => {
const path = link.getAttribute("path");
this.pushRoute(path);
});
});
}
pushRoute(path) {
window.history.pushState({}, null, path);
this.updateView();
}
init() {
window.addEventListener("load", this.updateView.bind(this));
// history模式 路由修改 浏览器会触发popstate事件
window.addEventListener("popstate", this.updateView.bind(this));
}
updateView(e) {
// console.log("history window.location", window.location);
// console.log("history window.history", window.history);
const currentUrl = window.location.pathname || "/";
this.routers[currentUrl] && this.routers[currentUrl]();
}
}
const routers = [
{
path: "/a",
compontent: `<div>我是a页面</div>`,
},
{
path: "/b",
compontent: `<div>我是b页面</div>`,
},
{
path: "/c",
compontent: `<div>我是c页面</div>`,
},
];
new Router(routers);
</script>
</body>
</html>
Abstract模式
支持所有javascript运行模式。vue-router 自身会对环境做校验,如果发现没有浏览器的 API,路由会自动强制进入 abstract 模式。在移动端原生环境中也是使用 abstract 模式。
总结
hash 模式和 history 模式都属于浏览器自身的特性,Vue-Router 只是利用了这两个特性(通过调用浏览器提供的接口)来实现前端路由。