- Published on
前端路由实现 - history篇
- Authors
- Name
- Yuga Sun
在上一篇 前端路由实现-hash 篇 已经介绍了如何通过 hash 的方式来实现前端路由,这一篇将在此基础上增加 history
路由的方式,想要实现此功能,必须先了解 History API。
History API
官方 History API 已经讲得很清楚了,我这里主要列举出来,方便参考。
History API 是 HTML5 新增的历史记录 API,它可以实现无刷新的更改地址栏链接,简单讲,就是当页面为 yugasun.com
, 执行 Javascript 语句:
window.history.pushState(null, null, '/about')
之后,地址栏地址就会变成 yugasun.com/about
, 但浏览器不会刷新页面,甚至不会检测目标页面是否存在。
History 对象属性
| 属性 | 只读 | 描述 | | ------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------ | | History.length | 是 | 返回一个整数,该整数表示会话历史中元素的数目,包括当前加载的页。例如,在一个新的选项卡加载的一个页面中,这个属性返回 1。 | | History.scrollRestoration | | 允许 Web 应用程序在历史导航上显式地设置默认滚动恢复行为。此属性可以是自动的(auto)或者手动的(manual)。 | | History.state | 是 | 返回一个表示历史堆栈顶部的状态的值。这是一种可以不必等待 popstate 事件而查看状态而的方式。 |
History 对象方法
| 方法 | 描述 | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | History.back() | 返回到上一条 history 记录,等同于: history.go(-1); | | History.forward() | 前进到吓一跳 history 记录,等同于: history.go(1); | | History.go() | 加载 history 中存储的指定标识的记录,以当前记录为基准 0,下一条为 1,上一条为-1,一次类推。 | | History.pushState() | 将指定的记录 push 到 history 记录栈中。三个参数分辨为: state object - 状态对象,title - 火狐浏览器已经忽略此参数,一般传入 null 值,URL - 新的历史记录的地址。 | | History.replaceState() | 替换当前的历史记录, 参数同 pushState 一致 |
实现原理
- 通常点击页面 a 链接,页面会刷新跳转,所以需要监听页面所有 a 链接点击事件,并阻止默认事件, 然后调用
history.pushState()
方法来实现路由切换
当活动历史记录条目更改时,将触发 popstate 事件, 需要注意的是,调用
history.pushState()
和history.replaceState()
不会触发popstate
事件。只有在做出浏览器动作时,才会触发该事件,如用户点击浏览器的回退/前进按钮。
代码实现
1.给上一篇中实现的 SPARouter
类构造函数添加一个参数 mode
(默认为 hash
), 如果传入值为 history
时, 我们就采用 history
路油方式。
constructor(el, routers, mode) {
this.mode = mode || 'hash';
//...
}
2.给 window 的popstate
添加事件监听,给所有 a
链接添加 click
事件监听
initEvent() {
window.addEventListener('load', () => {
console.log('load')
this.routeUpdate();
});
if (this.mode === 'history') {
window.addEventListener('popstate', (e) => {
console.log('popstate')
this.routeUpdate();
});
// 禁用所有a 链接默认跳转事件
let self = this;
document.addEventListener('click', function (e) {
let target = e.target || e.srcElement;
if (target.tagName === 'A') {
e.preventDefault(); // 这里阻止默认事件
let href = target.getAttribute('href');
let path = href.split('?')[0];
window
.history
.pushState({
path: path
}, null, href);
self.routeUpdate();
}
})
} else {
window.addEventListener('hashchange', () => {
console.log('hashchange')
this.routeUpdate();
});
}
}
3.新增获取当前 history state
方法:
getHistoryRoute() {
// 默认第一次加载时,获取不到state值,这里需要兼容处理
let path = (window.history.state && window.history.state.path) || '';
let queryStr = window
.location
.hash
.split('?')[1];
let params = queryStr
? queryStr.split('&')
: [];
let query = {};
params.map((item) => {
let temp = item.split('=');
query[temp[0]] = temp[1];
});
return {path: path, query: query};
}
4.对路由更新方法 routerUpdate
添加 mode
条件判断
routeUpdate() {
let getLocation = this.mode === 'history'
? this.utils.getHistoryRoute
: this.utils.getHashRoute;
let currentLocation = getLocation();
this.currentRoute.query = currentLocation['query']
this
.routers
.map((item) => {
if (item.path === currentLocation.path) {
this.currentRoute = item;
this.refresh();Ï
}
});
if (!this.currentRoute.path) {
if (this.mode === 'history') {
window
.history
.pushState({
path: '/index'
}, null, '/index');
this.routeUpdate();
} else {
location.hash = '/index';
}
}
}
做完以上更新后,我们的 SPARouter
类就实现了 history
路由功能了,然后将页面中链接中删除 #
,然后实例化 SPARouter
时添加第三个参数为 history
, 如下:
var router = new SPARouter(el, routes, 'history')
运行效果图