基于History实现前端路由
History
是 HTML5 新增的标准,对比hash
它的展示更加优雅,但低版本 ie 还有兼容问题。
首先History
表示窗口浏览历史,可以通过pushState
方法添加历史记录,以及通过go
方法来实现跳转,还有popstate
事件可以监听到记录变更。
下面就来分析实现一个History路由
的基础是什么
- 它需要在变更地址的时候,不会导致文档直接跳转,例如
hash路由
的出现是因为 url 的hash
变更不会导致文档变化; - 需要有事件可以辅助我们监听到这个变化;
这两个要求History
都能很好满足,当使用pushState
方法添加一条记录的时候,只会导致浏览器的地址栏发生变化,但是不会跳转到这个地址,并且浏览器也不会检查这个地址,利用这个特性我们可以实现比较优雅的路由地址,例如https://xxx.com/a
,a
就是这个路由地址。
为了方便理解下面实现的代码,简短介绍一下pushState
pushState
pushState
方法,它有三个参数
- 第一个是
state
,表示发生popstate
事件的时候传递的对象; - title,当前浏览器已经忽略了,传递
''
就好了; - 网址,要更改的地址,注意要符合同源政策;
举个例子,当前网站的根路径是https://xxx.com
,我们使用pushState
添加一条记录
history.pushState({ path: "/abc" }, "", "/abc");
当前的网址就是https://xxx.com/abc
。
popstate
pushState
事件触发的条件是用户点击前进
、后退
按钮或者调用 History.back()
、History.forward()
、History.go()
方法时才会触发。
触发事件后有一个event
对象,它有一个state
属性,就是对应pushState
方法的第一个参数。
更多关于history
的信息可以查阅MDN
history 路由实现
class Router {
constructor() {
this.routes = new Map();
this.init();
}
change(e) {
// 防止为null
const { path } = e.state || {};
this.implement(path);
}
init() {
window.addEventListener("popstate", this.change.bind(this));
window.addEventListener("load", () => {
const { pathname } = location;
history.replaceState({ path: pathname }, "", pathname);
this.implement(pathname);
});
}
implement(path) {
if (!this.routes.has(path)) {
return;
}
const fn = this.routes.get(path);
typeof fn == "function" && fn.call(this, path);
}
go(num) {
history.go(num);
}
route(state, fn) {
this.routes.set(state, fn);
}
push(state) {
history.pushState({ path: state }, "", state);
this.implement(state);
}
replace(state) {
history.replaceState({ path: state }, "", state);
this.implement(state);
}
}
这一块比较简单就不做更多赘述了,唯一一点需要注意的就是页面加载完毕也需要监听一次,这里用了load事件
。
使用方法
<ul>
<li><a href="/">hash1</a></li>
<li><a href="/hash2">hash2</a></li>
<li><a href="/hash3">hash3</a></li>
</ul>
<div><button class="f">前进</button> <button class="b">后退</button></div>
const color = {
"/": "yellow",
"/hash2": "#333",
"/hash3": "#DDD"
};
const route = new Router();
route.route("/", function(e) {
document.body.style.background = color[e];
});
route.route("/hash2", function(e) {
document.body.style.background = color[e];
});
route.route("/hash3", function(e) {
document.body.style.background = color[e];
});
Array.from(document.links).forEach(fn => {
fn.addEventListener("click", e => {
e.preventDefault();
const href = fn.href;
const { pathname } = new URL(href);
route.push(pathname);
});
});
const backOff = document.querySelector(".b");
const forward = document.querySelector(".f");
backOff.addEventListener("click", () => route.go(-1));
forward.addEventListener("click", () => route.go(1));
这里注意,我直接屏蔽了a
链接的默认跳转,这个是防止它直接到其他文档。
最后
关于前端路由的两种实现,这里就抛砖引玉的讲解完成了,剩下的就是在这个原理上更加完善,例如对 404 页面的处理。
如果有帮助可以 star 一下。