写在前面
路由的作用
当URL发生变化时,动态地切换并展示不同的组件,在单页应用中模拟出多页应用的体验。
Hash路由
单页应用最早采用的路由方式之一,基于URL后面的#部分(哈希部分)来切换页面或者组件。通常因为地址栏带#而被认为不如History路由美观。
不过你真的了解Hash路由的优点吗?
-
无服务端介入(Serverless):URL中的哈希值不被Http请求所包含,当hash值变化时,服务器的执行逻辑不会受到影响,所以我们也称其为客户端路由(Client-side Routing);
-
兼容性好(Good compatibility):因为URL的哈希部分是浏览器的一个长期特性,这使得其兼容性很好,许多经典的官方项目中也会采用这种路由模式。
之前有用过Vue封装的Hash Router和History Router,但是也没深究过内部细节,最近学习完Hash Router,借此机会分享一下。
本文基于Hash Router的特点实现路由的几种行为:
- 创建Router类
- 注册路由
- 加载路由
- 初始化界面
正文
提到路由,就不得不说说相关的单页应用(Single Page Application)。
单页应用:是指一个页面的应用,加载页面时一次将所有html、css、js资源准备好,对于其余功能界面做组件化封装,运行时在不同的组件之间切换,刷新时也仅仅时重新加载部分资源(局部刷新),能给用户带来良好的交互体验。
在框架技术成熟的今天,开发人员十分注重开发效率和用户体验。单页应用对于追求用户体验的项目场景来说,是一个不错的选择。但是与多页应用相比,各有优劣。
背景知识就简单聊聊,接下来才是本篇的重头戏!
一、创建Router类
在一个Vue项目中引入路由依赖后,我们都会先去创建一个全局路由对象。那么为了我们能够拿到这样一个对象,得先创建一个HashRouter的类。
由于页面需要写JS逻辑,故而我们先创建一个html文件。
在HashRouter类的构造函数里面添加一个成员routes,作为存储路由规则的对象,这里面主要包括了路径(哈希值)以及对应的处理函数。
但是看着构造函数应该还缺了点东西?是什么呢?
这时候会有朋友要说,history都没有算啥路由啊,应该写个vue-router中常见的history。不错,是一个思路。但是说的太全了。
本文分析的只是Hash Router。Vue-Router中的history封装了许多方法,用于管理浏览器的历史访问路径和导航过渡情况,但是Hash Router的核心只是监听URL的哈希部分的变化情况。我们只需要抽离出其内部监听哈希值改变的逻辑即可。
所以我们还需要在构造函数中监听浏览器提供的hashchange事件,这样保证在创建一个类的示例,构造函数被调用一次,hashchange事件的监听自动开启。
这里的监听器有三个参数。
- 第一个参数是被监听的事件(哈希值改变)
- 第二个参数是监听器触发后的执行逻辑,由于load()函数是在window的事件监听器下调用,默认load()的this是指全局执行上下文,但是我们通过.bind(this)的方式成功将this指向了新建的HashRouter实例对象。
- 第三个参数,false表示第二个参数的逻辑将在hashchange冒泡阶段(hashchange事件触发后)执行。
这里可能有人不清楚为什么一定要使用.bind()修改this指向到HashRouter的实例对象上?this指向全局对象,让load()方法挂载在全局怎么了?
我们先想一个问题,我们拿到路由对象时,是否需要注册路由?答案是肯定的,那么问题来了,如果在路由对象上找不到该load方法怎么办?
凉拌,这样路由对象连hashchange事件都监听不了,所以,构造函数内部load()方法必须挂在当前实例对象上。
二、注册路由
既然已经写好了路由对象内部结构,那么应当完善注册路由的功能。
由于是哈希路由,我们就直接处理哈希值部分即可。将routes对象中hash值为特定内容的路由,逻辑置为一个函数callback。
这里其实很好理解,我们先把页面的简单结构完善:
对应的,下面我们就需要去注册这三个路由:
注册好了,就要去加载实际路由,不然只是纸上谈兵。
三、加载路由
回到刚刚针对hashchange事件的监听,我们需要一个load函数去加载路由到的页面。
实际也就是分这三种情况加载路由。
当路径下没有哈希值,去首页;
有对应哈希值,去对应页面;
错误的哈希值,不跳转。
对于获取到路径下的哈希值,JS提供了一个原生对象location。
其中.hash值#后面部分的/xxx就是我们所需要的,因此需要截取字符串。
let hash = location.hash.slice(1);
下面就需要分情况讨论:
这里可能就hander难理解一点,hander实际上就是注册路由时传入的回调函数(callback),没有hander就不能正确显示出需要的页面。当检测到hander存在时,其.call()就会被调用,hander函数理所当然也被调用了。
写到这,基本上的功能也就写完了。
能够正常显示内容,URL的哈希部分也正确切换。
四、初始化界面
我们还需要为初始界面注册路由,这样能够确保第一次进入能够展示内容。
对了最后不要忘记调用load()喔!
以上就是实现一个简单的Hash Router的效果。完整代码:
<script>
let container = document.querySelector("#container");
function updateContent(content) {
container.innerHTML = content;
}
class HashRouter{
constructor(){
//存储路由规则的对象
this.routes = {};
window.addEventListener('hashchange',
this.load.bind(this),false)
}
load(){
let hash = location.hash.slice(1);
let hander = null;
if(!hash){
//去首页
hander = this.routes['hash-router'];
}else{
//去其他页面
hander = this.routes[hash];
}
//特殊情况
//确保找到了对应内容后将hander绑定到调用其的对象上
hander && hander.call(this);
}
register(hash,callback = () => {}){
this.routes[hash] = callback;
}
registerIndex(callback = () => {}){
this.routes['hash-router'] = callback;
}
}
const router = new HashRouter();
router.registerIndex(() => updateContent("homepage"));
router.register('/page1', () => updateContent("page1"));
router.register('/page2', () => updateContent("page2"));
router.register('/page3', () => updateContent("page3"));
router.load();
</script>
总结
Hash Router的核心内容之一就是监听hashchange事件。
优点:无服务端介入,兼容性好。
缺点:搜索引擎可能不会索引URL的Hash部分,影响页面的排行。
感谢看到这里。如果你觉得以上内容对你有帮助,请点个小赞,这将是我持续创作的动力!