翼度科技»论坛 编程开发 JavaScript 查看内容

[JavaScript进阶] 路由跳转原理 之 Hash 模式

5

主题

5

帖子

15

积分

新手上路

Rank: 1

积分
15
路由跳转原理 之 Hash

一. 路由跳转的原理

首先讲讲路由跳转的原理, 其实没有什么神秘的, 以变量类比:
  1. // 首先定义一个变量名为 container , 赋予初始值 'index'
  2. let container = 'index';
  3. // 监听一个点击事件
  4. window.addEventListener('click', (e) => {
  5.   // 当点击事件的触发元素的 id 为 'index' 的时候
  6.         if (e.target.id === 'index') {
  7.     // 改变变量的值为 'index'
  8.           container = 'index';
  9.   }
  10.   // 当点击事件的触发元素的 id 为 'news' 的时候
  11.   else if (e.target.id === 'news') {
  12.                 // 改变变量的值为 'news'
  13.                 container = 'news';
  14.   }
  15. });
复制代码
在上文的代码中, 在监听到点击事件的时候, 会改变变量的值. 那么, 如果不再监听点击事件, 而是 监听页面路径改变 ; container 也不是一个变量而是一个HTML元素, 当监听回调触发时, 修改的是这个 container 元素内部的HTML片段, 那么其实就是路由跳转了:
  1. // 定义一套路由
  2. const ComponentIndex = `Index Page`;
  3. const ComponentNews = `News Page`;
  4. // 获取 `container` 容器
  5. let container = document.querySelector('#container');
  6. // 赋予 `container` 容器初始值
  7. container.innerHTML = ComponentIndex;
  8. // 监听一个页面跳转事件 (不存在 pageURLChange 事件, 仅作为一个示例)
  9. window.addEventListener('pageURLChange', (e) => {
  10.   // 假设传入的回调参数就是跳转的页面
  11.   
  12.   // 当跳转的页面是 'index' 时
  13.         if (e === 'index') {
  14.     // 将 container 容器的内部HTML片段修改为Index路由的内容
  15.           container.innerHTML = ComponentIndex;
  16.   }
  17.   // 当跳转的页面是 'news' 时
  18.   else if (e === 'news') {
  19.                 // 将 container 容器的内部HTML片段修改为News路由的内容
  20.                 container.innerHTML = ComponentNews;
  21.   }
  22. });
复制代码
二. Hash跳转的实现原理

路由跳转目前主要有两种, hash 模式和 history 模式, 这其实是对应了 JavaScript 中两种无刷新改变网页URL的方式: location.hash 和 history.pushState . 本文主要讲的就是第一种: Hash模式 .
Hash 模式和 History 模式原理都是一样的, 不过是监听页面路径跳转的方式不同而已.
History模式等有空了会写的...(咕
2.1 Hash是什么

URL 路径中可以存在 锚点 , 通过一个符号 # 表示. 当 URL 中存在锚点的时候, 锚点后面的字符串将不会被请求上服务器, 仅作为本地浏览器数据访问, 这个值被称为 Hash 值.
比如, 下面这两串网址访问百度服务器的时候, 百度都只会接收到 https://www.baidu.com/ 这一串地址请求, Hash 值并不会通过网络请求发送给服务器.
  1. https://www.baidu.com
  2. https://www.baidu.com#12345
复制代码

关于锚点的概念, 其实在初学 HTML 的时候就接触到了: 在学习锚元素  的时候其实就已经了解过了, 当时讲的是 a 元素可以通过 #id 跳转至页面的某一个 id 的位置, 这本质上就是利用到了锚点.
参考文档: [MDN - 文本片段]
2.2 改变 hash 的原理

通过 location.hash 属性, 可以更改页面的 Hash , 并且不会刷新页面只改变 URL 路径; 当 Hash 改变的时候, 会触发一个 hashchange 事件, 以此我们就可以通过监听 hashchange 事件事件去改变页面的内容了.
同时, 当页面 Hash 改变的时候, 也会向浏览器的访问历史添加一个记录, 所以也可能通过 history.go() 去控制页面访问历史.
因为这两个API都比较简单所以就不单独列出来说明了, 可以自行参照文档阅读. 直接看下文代码也是可以的, 都是一些很基础的应用并且我会作出一定的说明.
参考文档:
2.3 通过 hashchange 事件监听页面路径改变

2.3.1 location.hash

当 URL 中没有锚点的时候, 直接输出会输出空字符串:
  1. /* URL: www.baidu.com */
  2. console.log(location.hash);
  3. // -> ''
复制代码
当向没有锚点的 URL 改变 hash 时, 会自动添加锚点:
输入时不用添加锚点, 但是输出时会输出锚点(见下例).
  1. /* URL: www.baidu.com */
  2. location.hash = 'index';
  3. /* URL: www.baidu.com#index */
  4. console.log(location.hash);
  5. // -> '#index'
复制代码
通常为了让路由跳转后的 URL 更像一个地址, 我们会在 Hash 前添加一个斜杠 / :
  1. /* URL: www.baidu.com */
  2. location.hash = '/index';
  3. /* URL: www.baidu.com#/index */
复制代码
2.3.2 hashchange 事件

当页面中的 Hash 发生改变的时候, 会触发事件 hashchange , 在事件的回调参数 e 中有两个可以利用到的属性: e.newURL 和 e.oldURL . 见名思意, 分别是改变后的 URL 和改变前的 URL .
  1. // 监听 hash 改变事件window.addEventListener('hashchange', (e) => {  // 防止重复跳转  if (e.newURL === e.oldURL) {      return;  }    /*   * 判断完重复跳转的情况之后, 直接使用页面的 `location.hash` 就可以了   * e.newURL 是一个字符串, 获取 hash 还需要额外处理   * 之前说过输出 hash 的时候会输出锚点 所以通过 `.slice()` 方法将第一个锚点符号删除.   */  console.log(location.hash.slice(1))}/* URL: www.baidu.com */
  2. location.hash = '/index';
  3. /* URL: www.baidu.com#/index */// -> /index
复制代码
其实这个回调参数是可以不使用的, 因为如果 e.newURL === e.oldURL , Hash 根本不会发生改变, hashchange 事件也不会触发, 这里仅仅只是作为一个 示例 .
2.4 通过 history.go() 控制页面访问历史

每一次调用 location.hash 都会往浏览器中写入一条历史记录, 理所应当 history.go() 也能控制 Hash 改变产生的历史记录:
  1. /* URL: www.baidu.com */
  2. location.hash = '/index';
  3. /* URL: www.baidu.com#/index */location.hash = '/news';/* URL: www.baidu.com#/news */history.go(-1);/* URL: www.baidu.com#/index */history.go(-1);/* URL: www.baidu.com */
复制代码
三. 实现一个 HashRouter 库

在前文我们已经对 Hash 模式的路由跳转进行了简单的剖析, 现在可以试着做一个简易的 Router 路由跳转库了.
3.1 规范

首先, 我们需要对一些格式进行一定的规范, 这样我们就可以基于这些规范写一个标准库:
3.1.1 HTML 规范

对于 HTML , 我们使用 dataset 进行标记:

  • data-router-link-container: 表示一个路由跳转容器.

    • data-router-link: 表示一个路由跳转链接, 该属性的值就是跳转的路由地址.

  • data-router-view: 表示一个路由内容显示容器, 路由跳转后显示的内容会在该元素内显示.
3.1.2 JavaScript 规范

编写一个 HashRouter 类, 构造函数的参数 options 的类型为:
  1. options = {
  2.         routes: Array<{
  3.     path: string,
  4.     component: {
  5.             template: string,
  6.     }
  7.   }>
  8. }
复制代码

  • routes: 路由数组

    • path: 路由的路径
    • component: 路由组件的内容

      • template: 路由组件的 HTML 片段


参考文档:
3.2 编写库

这部分内容就简单讲了, 内容都在代码块中, 主要就是一个元素获取以及 dataset 的值获取.
下面的方法都是类 HashRouter 中的方法:
3.2.1 跳转路由
  1. /**
  2. * 监听路由跳转容器点击, 跳转路由
  3. *  绑定一个具有 [data-router-link-container] 属性的容器, 监听这个容器内冒泡出来的 `click` 事件
  4. *  当 `click` 事件触发时, 判断触发的元素是否有 `data-router-link` 属性
  5. *  如果存在, 则改变当前页面的 Hash 为 `data-router-link` 属性的值
  6. */
  7. bindRouterLinkEvent() {
  8.     // 找到具有 [data-router-link-container] 属性的容器
  9.     document.querySelector('[data-router-link-container]')
  10.         // 监听容器内冒泡出来的 click 事件
  11.         ?.addEventListener('click', (e) => {
  12.             // 排除非 [data-router-link] 属性的容器
  13.             if (!e.target.dataset['routerLink']) {
  14.                 return;
  15.             }
  16.             // 阻止标签跳转
  17.             e.preventDefault();
  18.             // 更改页面 Hash
  19.             window.location.hash = `${e.target.dataset['routerLink']}`
  20.         });
  21. }
复制代码
3.2.2 监听路由跳转
  1. /**
  2. * 监听 URL hash 的改变, 并且更新 [data-router-view] 容器内的 HTML 片段.
  3. */
  4. listenHashChange() {
  5.     window.addEventListener('hashchange', () => {
  6.         // 寻找跳转路径
  7.         const route = this.routes.find(
  8.             route => route.path === window.location.hash.slice(1)
  9.         );
  10.         // 如果找不到跳转路径, 报错
  11.         if (!route) {
  12.             console.error('找不到跳转路径');
  13.             return;
  14.         }
  15.         // 改变 [data-router-view] 容器内的 HTML 片段
  16.         const viewContainer = document.querySelector('[data-router-view]');
  17.         if (viewContainer) {
  18.             viewContainer.innerHTML = route.component.template;
  19.         }
  20.     })
  21. }
复制代码
3.2.3 浏览历史操作
  1. /**
  2. * 历史记录跳转
  3. *
  4. * @param {number} step - 跳转的步数
  5. *
  6. * @example HashRouter.go(1) 前进一步历史
  7. * @example HashRouter.go(-1) 后退一步历史
  8. */
  9. go(step) {
  10.     history.go(step);
  11. }
  12. /**
  13. * 历史记录跳转 - 后退一步
  14. */
  15. back(){
  16.     this.go(-1);
  17. }
  18. /**
  19. * 历史记录跳转 - 前进一步
  20. */
  21. forward(){
  22.     this.go(1);
  23. }
复制代码
3.3 HashRouter.js
  1. /* HashRouter.js */
  2. class HashRouter {
  3.     /**
  4.      * @constructor
  5.      * @param {{routes: [{path: string, component: {template: string}}]}} options
  6.      * */
  7.     constructor(options) {
  8.         this.routes = options.routes;
  9.         this.bindRouterLinkEvent();
  10.         this.listenHashChange();
  11.     }
  12.     /**
  13.      * 匹配路由
  14.      *  绑定一个具有 [data-router-link-container] 属性的容器, 监听这个容器内冒泡出来的 `click` 事件
  15.      *  当 `click` 事件触发时, 判断触发的元素是否有 `data-router-link` 属性
  16.      *  如果存在, 则改变当前页面的 Hash 为 `data-router-link` 属性的值
  17.      */
  18.     bindRouterLinkEvent() {
  19.         // 找到具有 [data-router-link-container] 属性的容器
  20.         document.querySelector('[data-router-link-container]')
  21.             // 监听容器内冒泡出来的 click 事件
  22.             ?.addEventListener('click', (e) => {
  23.                 // 排除非 [data-router-link] 属性的容器
  24.                 if (!e.target.dataset['routerLink']) {
  25.                     return;
  26.                 }
  27.                 // 阻止标签跳转
  28.                 e.preventDefault();
  29.                 // 更改页面 Hash
  30.                 window.location.hash = `${e.target.dataset['routerLink']}`
  31.             });
  32.     }
  33.     /**
  34.      * 监听 URL hash 的改变, 并且更新 [data-router-view] 容器内的 HTML 片段.
  35.      */
  36.     listenHashChange() {
  37.         window.addEventListener('hashchange', () => {
  38.             // 寻找跳转路径
  39.             const route = this.routes.find(
  40.                 route => route.path === window.location.hash.slice(1)
  41.             );
  42.             // 如果找不到跳转路径, 报错
  43.             if (!route) {
  44.                 console.error('找不到跳转路径');
  45.                 return;
  46.             }
  47.             // 改变 [data-router-view] 容器内的 HTML 片段
  48.             const viewContainer = document.querySelector('[data-router-view]');
  49.             if (viewContainer) {
  50.                 viewContainer.innerHTML = route.component.template;
  51.             }
  52.         })
  53.     }
  54.     /**
  55.      * 历史记录跳转
  56.      *
  57.      * @param {number} step - 跳转的步数
  58.      *
  59.      * @example HashRouter.go(1) 前进一步历史
  60.      * @example HashRouter.go(-1) 后退一步历史
  61.      */
  62.     go(step) {
  63.         history.go(step);
  64.     }
  65.     /**
  66.      * 历史记录跳转 - 后退一步
  67.      */
  68.     back(){
  69.         this.go(-1);
  70.     }
  71.     /**
  72.      * 历史记录跳转 - 前进一步
  73.      */
  74.     forward(){
  75.         this.go(1);
  76.     }
  77. }
  78. export default HashRouter;
复制代码
3.4 示例

目录结构:
|        HashRouter.js
|        index.html
  1. <nav  data-router-link-container>
  2.     <a  data-router-link="/index">Index</a>
  3.     <a  data-router-link="/news">News</a>
  4. </nav>
  5. <hr>
复制代码


3.5 一些问题

HashRouter.js 存在的一些问题, 提供思考, 感兴趣的也可以想一想如何改造 HashRouter.js 使其功能更加强大:


  • 无法传参
  • 无法实现子路由
  • 无法通过函数跳转路由
  • ...

来源:https://www.cnblogs.com/Yiero/p/17818698.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具