手写Hash Router实战—我的学习心得分享
最近,我在Vue的学习中,学会了使用router。虽然我已经掌握了Vue Router的使用方法,知道了如何通过改变路径来导航,但我不知道路由背后是如何运作的,并且也不知道hash路由和history路由有什么区别。
于是我决定深入研究,不仅仅满足于使用现成的工具,所以我用html+js语言,手写了一个简单的Hash Router。我想通过这个过程更好的理解路由的工作原理,以及它是如何让SPA如此高效和响应迅速的。
初识HTTP协议与SPA的挑战
刚开始学习Web开发时,我就了解到HTTP协议的无状态性。每次用户请求新资源,浏览器都需要重新发送请求,服务器再返回整个HTML页面。这种方式在多页应用(MPA)中很常见,但对于追求高效和流畅体验的SPA来说,却显得有些力不从心。
为了给用户提供一种无缝的浏览体验,SPA不能在每次页面跳转时都重新加载整个页面。我开始寻找解决方案,最终发现了锚链接和hashchange事件,它们成为了SPA实现路由的关键技术。
了解路由
其核心有 hash 和 history 两种模式,hash 模式通过监听 hashchange 事件实现,history 模式通过监听 popstate 事件再使用 pushstate 修改 URL 来实现
Hash 模式:
Hash 模式利用了浏览器的 hashchange 事件来监听 URL 中 hash (#) 符号后面部分的变化。当 hash 发生变化时,事件被触发,然后 SPA 可以根据新的 hash 值来渲染相应的页面内容。这种方式不需要服务器端做任何处理,因为服务器只需要返回一个静态的 HTML 文件,剩下的路由逻辑由前端代码处理。
History 模式:
History 模式使用 HTML5 的 History API(包括 pushState, replaceState, 和 popstate 事件)来改变浏览器的历史记录栈,从而改变浏览器地址栏显示的 URL,但不会重新加载页面。这样可以创建更干净、更像传统多页面应用的 URL。然而,使用这种模式需要服务器端的支持,因为在用户直接访问某个 URL 或刷新页面时,服务器必须能够正确地响应并返回包含前端路由逻辑的静态文件或页面
构建Hash Router
在SPA中,路由机制负责在不重新加载整个页面的情况下,根据URL的变化动态更新页面内容。我决定亲自实践,以加深对这一机制的理解。有了理论依据,我开始动手实践。我首先创建了一个简单的HTML页面,其中包含一个导航菜单和一个用于展示内容的容器。导航菜单中的每个链接都有一个特定的哈希值,如#/page1
、#/page2
等。接下来,我编写了HashRouter
类,这是我的学习中最重要的一步。
创建HashRouter
类
class HashRouter {
constructor() {
this.routes = {}; // 存储路由表
window.addEventListener('hashchange', this.load.bind(this), false);
}
// 注册路由
register(hash, callback = function () { }) {
this.routes[hash] = callback;
}
// 注册首页路由
registerIndex(callback = function () { }) {
this.routes['index'] = callback;
}
// 加载路由
load() {
let hash = location.hash.slice(1); // 获取当前URL的hash
let handler = this.routes[hash] || this.routes['index']; // 获取对应的处理函数
handler && handler.call(this); // 执行处理函数
}
}
我定义了HashRouter
类,它包含以下关键部分:
-
构造函数:在这里,我初始化了一个
routes
对象,用于存储不同哈希值对应的处理函数。同时,我添加了一个事件监听器,用于监听hashchange
事件。 -
hashchange
是什么?hashchange 事件是在浏览器的 URL 中的片段标识符(通常被称为“hash”)发生变化时触发的。片段标识符是 URL 中 # 符号后面的部分。 hashchange 事件非常有用,尤其是在单页面应用(SPA)中,因为它允许开发者检测和响应 URL 中 hash 的更改,而无需整个页面重新加载。这使得在 SPA 中实现平滑的页面导航成为可能,因为可以根据 hash 的变化动态更新页面内容。 -
register
方法:允许我为特定的哈希值注册一个处理函数,这样当该哈希值被触发时,就能执行相应的逻辑。 -
registerIndex
方法:用于注册首页的处理函数,以防哈希值为空的情况。 -
load
方法:这是核心的路由处理器,负责读取当前的哈希值,并执行相应的处理函数。它检查 routes 对象中是否存在与当前 hash 匹配的处理函数。如果没有找到匹配的 hash,则使用 'index' 作为默认键来查找处理函数。如果找到了处理函数,就调用它。这里使用了 && 运算符来确保只有当 handler 不是 undefined 或 null 时才执行函数。
在创建这个类的时候,我一开始忘记了this的指向问题,直接在window.addEventListener('hashchange', this.load.bind(this), false)
里面调用了load函数写成了window.addEventListener('hashchange', this.load(), false)
,
我又狠狠的恶补了一下this的指向,希望大家不要在这个地方碰壁了,哦对了,其实还可以写成箭头函数的形式就不用考虑this指向了
实现和测试
在HTML中,我需要一个容器来显示当前路由对应的视图组件。我在页面中添加了一个div元素,并将其ID设置为container。
<div id="container"></div>
接下来,我实例化了HashRouter
类,并为首页和每个页面注册了处理函数。这些函数实际上就是简单的DOM操作,用于更新container
元素的内容。最后,我调用了router.load()
,以确保页面加载时可以正确显示首页内容。
let router = new HashRouter();
let container = document.getElementById('container');
router.registerIndex(() => container.innerHTML = '我是首页');
router.register('/page1', () => container.innerHTML = "我是page1");
router.register('/page2', () => container.innerHTML = '我是Page2');
router.register('/page3', () => container.innerHTML = "我是page3");
最后,我在页面加载时初始化了路由,确保用户在访问页面时能够看到正确的视图。
router.load();
测试 完成上述步骤后,我进行了几个关键的测试来验证HashRouter的功能:
- 首页显示:确认页面加载时正确显示首页内容。
- 页面切换:通过点击导航链接,检查是否能正确切换到#/page1、#/page2和#/page3对应的页面。
- URL变化:确保浏览器地址栏的URL哈希值会随着页面切换而变化。
- 刷新页面:在页面切换后刷新浏览器,检查是否能够正确回到刷新前的页面。
通过这一系列的测试,我确保了HashRouter的稳定性和可靠性,同时也加深了对单页应用中路由管理的理解。
结语
通过这次学习,我不仅掌握了如何手写一个Hash Router,更重要的是,我理解了前端路由背后的逻辑和原理。这不仅仅是关于代码的胜利,更是对Web工作方式的一次深刻洞察。
转载自:https://juejin.cn/post/7389643363160686602