简易路由实现——(history路由)

History 是 HTML5 新增的标准,对比 hash 它的展示更加优雅,但低版本 ie 还有兼容问题。
History 的 pushState,replacestate 方法可以添加修改历史记录且不会发送新的服务器请求,因此可以利用此特性实现前端路由跳转。
History 的 go ,back, forward 方法可以实现跳转,后退,前进功能,还有 popstate 事件可以监听到记录变更。
页面结构
由于 a 标签 会导致页面页面跳转,因此页面结构需改写一番,使用插件方法进行路由跳转

复制代码实现 history 路由 init() 在 MDN 上,是这样介绍 popstate 的

当活动历史记录条目更改时,将触发 popstate 事件。如果被激活的历史记录条目是通过对 history.pushState()的调用创建的,或者受到对 history.replaceState()的调用的影响,popstate 事件的 state 属性包含历史条目的状态对象的副本。
需要注意的是调用 history.pushState() 或 history.replaceState() 不会触发 popstate 事件。只有在做出浏览器动作时,才会触发该事件,如用户点击浏览器的回退按钮(或者在 Javascript 代码中调用 history.back() 或者 history.forward() 方法)
不同的浏览器在加载页面时处理 popstate 事件的形式存在差异。页面加载时 Chrome 和 Safari 通常会触发 popstate 事件,但 Firefox 则不会。

因此在 history 路由的初始化方法中,需要对 popstate 和 load 事件进行监听
export default class HistoryRouter extends RouterParent {
constructor(routerConfig) {
super(routerConfig);
}

init() {
    // refresh 实现对应组件和当前路由绑定显示
    // bind(this) 传入此实例对象,否则this指向有问题
    window.addEventListener('popstate', this.refresh.bind(this), false);
    window.addEventListener('load', this.refresh.bind(this), false);
}

}
复制代码refresh()
与 hash 路由实现一致,这里是对组件控制显示隐藏,只不过在这里可以直接使用 history 的功能, 不用自己建立 routeHistory 来控制跳转
refresh() {
let path = window.location.pathname,
currentComponentName = ‘’,
nodeList = document.querySelectorAll(’[data-component-name]’);
// 找出当前路由的名称
for (let i = 0; i < this._routes.length; i++) {
if (this._routes[i].path === path) {
currentComponentName = this._routes[i].name;
break;
}
}
// 根据当前路由的名称显示对应的组件
nodeList.forEach(item => {
if (item.dataset.componentName === currentComponentName) {
item.style.display = ‘block’;
} else {
item.style.display = ‘none’;
}
});
}
复制代码back() && front()
后退前进直接调用 history 的 api 即可,此时会触发 popstate 事件调用 refresh 方法渲染页面
back() {
window.history.back();
}
front() {
window.history.forward();
}
复制代码push(option)
在vue-router中,可以通过 path, name 修改当前路由,并且可以携带 query 参数
因此优先判断 path, 如果有 path, 则直接调用 pushState 添加历史记录; 没有 path, 则根据 name 从 routes 中找出 path, 再调用 pushState 添加历史记录。因为 history.pushState() 或 history.replaceState() 不会触发 popstate,因此我们需要手动调用一下 refresh 方法
push(option) {
if (option.path) {
// 绑定this指向,使函数可以调用类的方法
pushHistory.call(this, option.path,option.query);
} else if (option.name) {
let routePath = ‘’;
// 根据路由名称找路由path
for (let i = 0; i < this._routes.length; i++) {
if (this._routes[i].name === option.name) {
routePath = this._routes[i].path;
break;
}
}
if (!routePath) {
error(‘组件名称不存在’);
} else {
pushHistory.call(this, routePath, option.query);
}
}
}

// 路由跳转
function pushHistory(routePath, query) {
let path = getTargetPath(routePath, query);
if (path !== window.location.pathname) {
window.history.pushState(path, ‘’, path);
this.refresh();
}
}

function error(message) {
typeof console !== ‘undefined’ && console.error([html-router] ${message});
}

// 获取即将跳转的路径
function getTargetPath(path, query) {
if (!query) return path;
let str = ‘’;
for (let i in query) {
str += ‘&’ + i + ‘=’ + query[i];
}
return path + ‘?’ + str.slice(1);
}

复制代码replace(option)
replace 和 push 的逻辑基本一致,只是调用的不是 pushState,而是 replaceState 方法。因此对 push 方法改造一下,使其兼容 replace
replace(option) {
// 表示当前处于replace
this.replaceRouter = true;
this.push(option);
}
push(option) {
if (option.path) {
pushHistory.call(this, option.path, option.query, this.replaceRouter);
} else if (option.name) {
let routePath = ‘’;
// 根据路由名称找路由path
for (let i = 0; i < this._routes.length; i++) {
if (this._routes[i].name === option.name) {
routePath = this._routes[i].path;
break;
}
}
if (!routePath) {
error(‘组件名称不存在’);
} else {
pushHistory.call(this, routePath, option.query, this.replaceRouter);
}
}
}

// 改写路由跳转
function pushHistory(routePath, query, replace) {
let path = getTargetPath(routePath, query);
if (path !== window.location.pathname) {
if (replace) {
window.history.replaceState(path, ‘’, path);
this.replaceRouter = false;
} else window.history.pushState(path, ‘’, path);
this.refresh();
}
}
复制代码demo 测试
测试代码就不写了,与前文 hash 路由一致,效果如下:

但是在这里发现一个问题,当处于某个路由时,刷新页面,会出现下面这种情况

一刷新就会出现404,在 vue-router官方文档 中也有介绍,开启 history 需要服务端支持!

当你使用 history 模式时,URL 就像正常的 url,例如 yoursite.com/user/id,也好看…
不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 oursite.com/user/id 就会返回 404,这就不好看了。
所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。

你可能感兴趣的:(简易路由实现——(history路由))