HashRouter的学习及手写

337 阅读4分钟

前言

在写前端代码初期,跳转页面都是用a标签加链接实现点击跳转,但是遇到无需重新加载整个页面,只需更新部分内容或者动态内容加载的时候,直接跳转新页面的操作过于费时和不便,因此,接下来我们学习一种更便捷好用的技术——HashRouter。

HashRouter的学习

什么是HashRouter?

HashRouter 是一种在单页应用(SPA)中实现前端路由的技术,它利用了URL中的哈希部分(#)来模拟不同的页面状态,而无需实际请求新的HTML文档。这种技术对于早期的SPA非常有用,因为它绕过了服务器端的路由逻辑,允许客户端直接处理URL变化。

以下是 HashRouter 的工作原理和关键点:

HashRouter 工作原理

  1. URL中的哈希部分:在URL中,#符号后面的部分被称为哈希部分,例如,在URL https://example.com/#/page2 中,#/page2 就是哈希部分。
  2. hashchange 事件:每当URL的哈希部分发生变化时,浏览器会触发 hashchange 事件。前端代码可以监听这个事件,然后根据哈希值的变化来更新页面的内容。
  3. 客户端路由逻辑:在 hashchange 事件处理器中,可以编写逻辑来解析哈希值,并根据解析结果决定展示哪个组件或视图。这通常涉及到查找一个路由表或路由映射,将哈希值映射到相应的视图组件上。
  4. 更新视图:根据解析后的哈希值,使用 Vue 的 <router-view> 组件或其他机制来更新显示的视图,而无需重新加载整个页面。

优缺点

优点

  • 无需服务器端路由支持:HashRouter 不需要服务器端的配合,所有的路由逻辑都在客户端实现,这使得部署和维护变得更加简单。
  • 兼容性好:几乎所有的浏览器都支持 hashchange 事件,因此 HashRouter 在各种浏览器上的表现更加一致。

缺点

  • SEO(搜索引擎优化)问题:由于URL的哈希部分不会被包含在服务器返回的原始HTML中,这可能会导致搜索引擎无法正确索引SPA中的各个“页面”。
  • 用户体验:虽然SPA提供了流畅的用户体验,但某些用户可能不习惯没有完整页面加载的感觉,特别是在复杂的操作或大型应用中。

手写HashRouter

HTML结构

  • 页面包含一个导航栏(<nav>),其中包含三个链接到不同哈希值的锚点(<a>标签)。
  • 页面主体部分(<div id="container">)用于展示根据当前哈希值动态加载的内容。

JavaScript逻辑

HashRouter
  • 构造函数:初始化一个空的路由表(routes),并监听hashchange事件,以便在哈希值发生变化时调用load方法。
  • register方法:接受一个哈希值和一个回调函数,将它们添加到路由表中。
  • registerIndex方法:为默认或空哈希值设置回调函数。
  • load方法:读取当前的哈希值,从路由表中查找对应的回调函数,并执行它。如果没有找到匹配项,则执行首页的回调函数。
初始化和注册路由
  • 创建HashRouter实例router
  • 获取页面容器元素container
  • 注册首页、/page1/page2/page3的路由回调,每个回调都会更新containerinnerHTML属性,显示对应页面的内容。
  • 打印路由表router.routes
  • 初始加载页面内容。
功能描述

当用户点击导航链接或直接修改URL的哈希值时,浏览器会触发hashchange事件。HashRouter监听此事件并通过load方法检查当前哈希值,从而决定加载哪个页面的内容到container元素中。这种方式允许用户在不完全刷新页面的情况下浏览不同的虚拟“页面”。

源代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>手写Hash Router</title>
</head>

<body>
    <nav id="nav">
        <ul>
            <li><a href="#/page1">page1</a></li>
            <li><a href="#/page2">page2</a></li>
            <li><a href="#/page3">page3</a></li>
        </ul>
    </nav>
    <!-- router view -->
    <div id="container">
        <script>
            /**
             * HashRouter类用于实现基于URL哈希值的路由管理。
             * 它监听hashchange事件,根据URL的哈希部分加载对应的处理函数。
             */
            class HashRouter {
                /**
                 * 构造函数初始化路由表,并设置hashchange事件监听器。
                 */
                constructor() {
                    this.routes = {}; // page => Component
                    window.addEventListener('hashchange',
                        this.load.bind(this), false)
                }

                /**
                 * 注册一个路由处理函数。
                 * @param {string} hash - URL的哈希值部分。
                 * @param {Function} callback - 当路由匹配到指定hash时执行的函数。
                 */
                register(hash, callback = function() {}) {
                    this.routes[hash] = callback;
                }

                /**
                 * 注册默认的路由处理函数,用于处理空哈希值。
                 * @param {Function} callback - 当路由为空时执行的函数。
                 */
                registerIndex(callback = function() {}) {
                    this.routes['index'] = callback
                }

                /**
                 * 根据当前URL的哈希值加载对应的路由处理函数。
                 */
                load() {
                    // console.log(location.hash); // BOM
                    let hash = location.hash.slice(1) // 去掉# 方是路由
                    console.log(hash, '////');
                    let handler;
                    if (!hash) {
                        // 首页
                        handler = this.routes['index']
                    } else {
                        // 相应页面
                        handler = this.routes[hash]
                    }
                    handler && handler.call(this)
                }
            }

            let router = new HashRouter();
            let container = document.getElementById('container');
            // 注册首页路由处理函数
            router.registerIndex(() => container.innerHTML = '我是首页')
            // 注册page1路由处理函数
            router.register('/page1', () => container.innerHTML = '我是Page1')
            // 注册page2路由处理函数
            router.register('/page2', function() {
                // console.log(this, this.routes)
                container.innerHTML = '我是Page2'
            })
            // 注册page3路由处理函数
            router.register('/page3', () => container.innerHTML = '我是Page3')
            console.log(router.routes);
            // 初始加载路由
            router.load()
        </script>
    </div>
</body>

</html>

效果展示

image.png