我们在使用 vue 或者 react 等前端渲染时,通常会有 hash 路由和 history 路由两种路由方式。
hash 路由:监听 url 中 hash 的变化,然后渲染不同的内容,这种路由不向服务器发送请求,不需要服务端的支持;
history 路由:监听 url 中的路径变化,需要客户端和服务端共同的支持;
我们一步步实现这两种路由,来深入理解下底层的实现原理。我们主要实现以下几个简单的功能:
监听路由的变化,当路由发生变化时,可以作出动作;
可以前进或者后退;
可以配置路由;
当页面中的 hash 发生变化时,会触发hashchange事件,因此我们可以监听这个事件,来判断路由是否发生了变化。
window.addEventListener(
'hashchange',
function (event) {
const oldURL = event.oldURL; // 上一个URL
const newURL = event.newURL; // 当前的URL
console.log(newURL, oldURL);
},
false
);
对 oldURL 和 newURL 进行拆分后,就能获取到更详细的 hash 值。我们这里从创建一个 HashRouter 的 class 开始一步步写起:
class HashRouter {
currentUrl = ''; // 当前的URL
handlers = {};
getHashPath(url) {
const index = url.indexOf('#');
if (index >= 0) {
return url.slice(index + 1);
}
return '/';
}
}
事件hashchange只会在 hash 发生变化时才能触发,而第一次进入到页面时并不会触发这个事件,因此我们还需要监听load事件。这里要注意的是,两个事件的 event 是不一样的:hashchange 事件中的 event 对象有 oldURL 和 newURL 两个属性,但 load 事件中的 event 没有这两个属性,不过我们可以通过 location.hash 来获取到当前的 hash 路由:
class HashRouter {
currentUrl = ''; // 当前的URL
handlers = {};
constructor() {
this.refresh = this.refresh.bind(this);
window.addEventListener('load', this.refresh, false);
window.addEventListener('hashchange', this.refresh, false);
}
getHashPath(url) {
const index = url.indexOf('#');
if (index >= 0) {
return url.slice(index + 1);
}
return '/';
}
refresh(event) {
let curURL = '',
oldURL = null;
if (event.newURL) {
oldURL = this.getHashPath(event.oldURL || '');
curURL = this.getHashPath(event.newURL || '');
} else {
curURL = this.getHashPath(window.location.hash);
}
this.currentUrl = curURL;
}
}
到这里已经可以实现获取当前的 hash 路由,但路由发生变化时,我们的页面应该进行切换,因此我们需要监听这个变化:
class HashRouter {
currentUrl = ''; // 当前的URL
handlers = {};
// 暂时省略上面的代码
refresh(event) {
// 当hash路由发生变化时,则触发change事件
this.emit('change', curURL, oldURL);
}
on(evName, listener) {
this.handlers[evName] = listener;
}
emit(evName, ...args) {
const handler = this.handlers[evName];
if (handler) {
handler(...args);
}
}
}
const router = new HashRouter();
rouer.on('change', (curUrl, lastUrl) => {
console.log('当前的hash:', curUrl);
console.log('上一个hash:', lastUrl);
});
到这里,我们把基本的功能已经完成了。来配合一个例子就更形象了:
// 先定义几个路由
const routes = [
{
path: '/',
name: 'home',
component: <Home />,
},
{
path: '/about',
name: 'about',
component: <About />,
},
{
path: '*',
name: '404',
component: <NotFound404 />,
},
];
const router = new HashRouter();
// 监听change事件
router.on('change', (currentUrl, lastUrl) => {
let route = null;
// 匹配路由
for (let i = 0, len = routes.length; i < len; i++) {
const item = routes[i];
if (currentUrl === item.path) {
route = item;
break;
}
}
// 若没有匹配到,则使用最后一个路由
if (!route) {
route = routes[routes.length - 1];
}
// 渲染当前的组件
Reactdom.render(route.component, document.getElementById('app'));
});
查看【hash 路由的样例】。
在 history 路由中,我们一定会使用window.history中的方法,常见的操作有:
back():后退到上一个路由;
forward():前进到下一个路由,如果有的话;
go(number):进入到任意一个路由,正数为前进,负数为后退;
pushState(obj, title, url):前进到指定的 URL,不刷新页面;
replaceState(obj, title, url):用 url 替换当前的路由,不刷新页面;
调用这几种方式时,都会只是修改了当前页面的 URL,页面的内容没有任何的变化。但前 3 个方法只是路由历史记录的前进或者后退,无法跳转到指定的 URL;而pushState和replaceState可以跳转到指定的 URL。如果有面试官问起这个问题“如何仅修改页面的 URL,而不发送请求”,那么答案就是这 5 种方法。
如果服务端没有新更新的 url 时,一刷新浏览器就会报错,因为刷新浏览器后,是真实地向服务器发送了一个 http 的网页请求。因此若要使用 history 路由,需要服务端的支持。
pushState 和 replaceState 两个方法跟 location.href 和 location.replace 两个方法有什么区别呢?应用的场景有哪些呢?
location.href 和 location.replace 切换时要向服务器发送请求,而 pushState 和 replace 仅修改 url,除非主动发起请求;
仅切换 url 而不发送请求的特性,可以在前端渲染中使用,例如首页是服务端渲染,二级页面采用前端渲染;
可以添加路由切换的动画;
在浏览器中使用类似抖音的这种场景时,用户滑动切换视频时,可以静默修改对应的 URL,当用户刷新页面时,还能停留在当前视频。
当我们用 history 的路由时,必然要能监听到路由的变化才行。全局有个popstate事件,别看这个事件名称中有个 state 关键词,但pushState和replaceState被调用时,是不会触发触发 popstate 事件的,只有上面列举的前 3 个方法会触发。可以点击【popState 不会触发 popstate 事件】查看。
针对这种情况,我们可以使用window.dispatchEvent添加事件:
const listener = function (type) {
var orig = history[type];
return function () {
var rv = orig.apply(this, arguments);
var e = new Event(type);
e.arguments = arguments;
window.dispatchEvent(e);
return rv;
};
};
window.history.pushState = listener('pushState');
window.history.replaceState = listener('replaceState');
然后就可以添加对这两个方法的监听了:
window.addEventListener('pushState', this.refresh, false);
window.addEventListener('replaceState', this.refresh, false);
完整的代码如下:
class HistoryRouter {
currentUrl = '';
handlers = {};
constructor() {
this.refresh = this.refresh.bind(this);
this.addStateListener();
window.addEventListener('load', this.refresh, false);
window.addEventListener('popstate', this.refresh, false);
window.addEventListener('pushState', this.refresh, false);
window.addEventListener('replaceState', this.refresh, false);
}
addStateListener() {
const listener = function (type) {
var orig = history[type];
return function () {
var rv = orig.apply(this, arguments);
var e = new Event(type);
e.arguments = arguments;
window.dispatchEvent(e);
return rv;
};
};
window.history.pushState = listener('pushState');
window.history.replaceState = listener('replaceState');
}
refresh(event) {
this.currentUrl = location.pathname;
this.emit('change', location.pathname);
document.querySelector('#app span').innerhtml = location.pathname;
}
on(evName, listener) {
this.handlers[evName] = listener;
}
emit(evName, ...args) {
const handler = this.handlers[evName];
if (handler) {
handler(...args);
}
}
}
const router = new HistoryRouter();
router.on('change', function (curUrl) {
console.log(curUrl);
});
使用方法与上面的 hash 路由一样,这里就不多赘述了。点击查看【history 路由的实现应用】
我们腾讯新闻中的抢金达人活动,就是采用的这种路由方式,页面的首次渲染采用服务端直出,二级跳转页面,使用前端 history 路由的前端渲染方式。
至此,两种路由的原理和实现方式都介绍完毕了,欢迎拍砖。
来自:https://www.xiabingbao.com/post/fe/hash-history-router.html
在使用vue-cli搭建的环境中,浏览器上URL地址中是存在#的,这是由于vue-router 默认 hash 模式,不难发现#的出现真的很丑陋。官网给出了如何使用history模式mode: history
vue中路由传参主要的3种方式:query方式(push时使用path来匹配)、params模式(push时使用name来匹配)、location预声明参数模式(push使用path来匹配,但是它跟params模式不同)
我们的通用的后台管理系统中,我们会根据权限的粗细不同,会对每个角色每个权限每个资源进行控制。同样的我们也需要实现一个这样的功能。 这篇文章我将主要讲vue端的实现,关于后台接口我就不会涉及,当我接触的时候我们的后台接口是springcloud实现。
前后端分离开发模式,后端会把路由控制丢在前端,这几天再开发单页面小的项目,手动撸了个路由。前端路由实现有两种方法。HTML5 History API包括2个方法:history.pushState()和history.replaceState(),和1个事件:window.onpopstate。hash + location.onhashchange
在后台管理系统中,一般都会采用权限管理。路由菜单数据都会保存到数据库中,在vue-router 2.2版本新增了一个router.addRoutes(routes)方法,即可用它来实现动态路由了
vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。
原本想用动态路由的思路去做,按权限加载对应路由表,但是由于权限可以交叉(比如一个人可以同时是主题管理员和数据服务管理员),导致权限路由表还是得去做判断组合。于是放弃了这个思路,索性就在beforeEach里直接判断了。
使用vue-cli构建项目后,我们会在Router文件夹下面的index.js里面引入相关的路由组件,webpack在打包的时候会把整个路由打包成一个js文件,如果页面一多,会导致这个文件非常大,加载缓慢
query和params的区别,query相当于get请求,在页面跳转的时候,可以在地址栏看到请求参数,然而params则相当于post请求,参数不会在地址栏中显示。
当服务端接收到HTTP请求时,可以通过onRequest() 获取到url, pathname,query,及paramParams参数;为了解析这些数据需要使用url和querystring模块
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!