前端路由 Hash 模式和 History 模式

news2024/10/5 0:55:11

在SPA单页面模式盛行,前后端分离的背景下,我们要弄清楚路由到底是个什么玩意,它可以帮助我们加深对于前端项目线上运作的理解。
而现在我们常见的路由实现方式,主要有两种,分别是historyhash模式。

理解

如何理解路由的概念,其实还是要从单页面着手,进行剖析。
单页面,说白了,就是指我们的服务只有一个index.html静态文件,这个静态文件前端生成后,丢到服务器上面。用户访问的时候,就是访问这个静态页面。而静态页面中所呈现出来的所有交互,包括点击跳转,数据渲染等,都是在这个唯一的页面中完成的。
这里我用一张常见的单页应用图,可能更方便我们理解。
在这里插入图片描述

例子中可以看到有四个模块,点击每个模块,内容区域都展示对应的内容,并且浏览器在点击的时候并没有发出实际的http请求(不包括其他静态资源,只是针对页面层面),只有第一次发出的请求获得的静态文件index.html。那么问题来了,内容区域是如何认识到用户点击了不同的模块,从而更新内容区域,并且做到不需要再次发出请求呢?

这个其实就是路由做的工作:
通过一定的机制,监听用户的行为动作,从而做出对应的变化。

hash模式

我们都知道一个URL是由很多部分组成,包括协议域名路径queryhash等,比如上面的例子,我们点击不同模块的时候可能看到是这样的URL

https://domain.xxx.com/index.html/#/ //首页
https://domain.xxx.com/index.html/#/news //新闻
https://domain.xxx.com/index.html/#/articles // 文章
https://domain.xxx.com/index.html/#/chat // 聊天

#号后面的,就是一个URL中关于hash的组成部分,可以看到,不同路由对应的hash是不一样的,但是它们都是在访问同一个静态资源index.html。我们要做的,就是如何能够监听到URL中关于hash部分发生的变化,从而做出对应的改变。
通过监听hashchange方法,在hash改变的时候,触发该事件。有了监听事件,且改变hash页面并不刷新,这样我们就可以在监听事件的回调函数中,执行我们展示和隐藏不同UI显示的功能,从而实现前端路由。
下面是关于hash路由的核心实现,可以看出来,主要就是监听hash的变化,渲染不同的组件代码

  class HashRouter {
    constructor(routes = []) {
      this.routes = routes
      this.currentHash = '/'
      this.refresh = this.refresh.bind(this)
      window.addEventListener('load', this.refresh, false)
      window.addEventListener('hashchange', this.refresh, false)
    }
    getUrlHash(url) {
      return url.indexOf('#') > -1 ? url.slice(url.indexOf('#') + 1) : '/'
    }
    refresh(event) {
      const { newURL = '', oldURL = '' } = event

      const newHash = this.getUrlHash(newURL ? newURL : window.location.hash)
      this.currentHash = newHash
      this.mountComponent()
    }
    mountComponent() {
      const currentRoute = this.routes.find(route => route.path === this.currentHash)
      const route = currentRoute ?? this.routes[0]
      document.querySelector('#content').innerHTML = route.component
    }
  }
  const router = new HashRouter([
    {
      path: '/',
      name: 'Home',
      component: '<div>首页-Home</div>'
    },
    {
      path: '/news',
      name: 'News',
      component: '<div>新闻-News</div>'
    },
    {
      path: '/articles',
      name: 'Articles',
      component: '<div>文章-Artices</div>'
    },
    {
      path: '/chat',
      name: 'Chat',
      component: '<div>聊天-Chat</div>'
    }
  ])
结论:
  • hash模式所有的工作都是在前端完成的,不需要后端服务的配合
  • hash模式的实现方式就是通过监听URL中hash部分的变化,从而做出对应的渲染逻辑
  • hash模式下,URL中会带有#,看起来不太美观

history模式

history路由模式的实现,是要归功于HTML5提供的一个history全局对象,可以将它理解为其中包含了关于我们访问网页(历史会话)的一些信息。同时它还暴露了一些有用的方法,比如:

window.history.go // 可以跳转到浏览器会话历史中的指定的某一个记录页
window.history.forward // 指向浏览器会话历史中的下一页,跟浏览器的前进按钮相同
window.history.back // 返回浏览器会话历史中的上一页,跟浏览器的回退按钮功能相同
window.history.pushState // 可以将给定的数据压入到浏览器会话历史栈中
window.history.replaceState // 将当前的会话页面的url替换成指定的数据

而history路由的实现,主要就是依靠于pushStatereplaceState实现的,这里我们先总结下它们的一些特点

  • 都会改变当前页面显示的url,但都不会刷新页面
  • pushState是压入浏览器的会话历史栈中,会使得history.length加1,而replaceState是替换当前的这条会话历史,因此不会增加history.length

既然已经能够通过pushState或replaceState实现改变URL而不刷新页面,那么是不是如果我们能够监听到改变URL这个动作,就可以实现前端渲染逻辑的处理呢?这个时候,我们还要了解一个事件处理程序popstate,先看下它的官方定义

每当激活同一文档中不同的历史记录条目时,popstate 事件就会在对应的 window 对象上触发。如果当前处于激活状态的历史记录条目是由 history.pushState() 方法创建的或者是由 history.replaceState() 方法修改的,则 popstate 事件的 state 属性包含了这个历史记录条目的 state 对象的一个拷贝。
调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件。popstate 事件只会在浏览器某些行为下触发,比如点击后退按钮(或者在 JavaScript 中调用 history.back() 方法)。即,在同一文档的两个历史记录条目之间导航会触发该事件。

也就是说:

  • history.pushState和history.replaceState方法是不会触发popstate事件的,但是浏览器的某些行为会导致popstate,比如go、back、forward
  • popstate事件对象中的state属性,可以理解是我们在通过history.pushState或history.replaceState方法时,传入的指定的数据

重写history.pushStatehistory.replaceState方法,在这个方法也能够暴露出自定义的全局事件,然再监听自定义的事件

const rewrite = function(type) {
   let origin = history[type]
   return function() {
      const res = origin.apply(this, arguments)
      const e = new Event(type)
      e.arguments = arguments
      window.dispatchEvent(e)
      return res
   }
}

 history.pushState = rewrite ('pushState')
 history.replaceState = rewrite ('replaceState')

执行完上面两个方法后,相当于将pushState和replaceState这两个监听器注册到了window上面,具体的定义可参考EventTarget.dispatchEvent

简易实现


  const nav1 = document.querySelector('#a1')
  const nav2 = document.querySelector('#a2')
  const nav3 = document.querySelector('#a3')
  const nav4 = document.querySelector('#a4')

  nav1.addEventListener('click', () => {
    history.pushState({ page_id: 1 }, '', '/home')
  })

  nav2.addEventListener('click', () => {
    history.pushState({ page_id: 2 }, '', '/news')
  })

  nav3.addEventListener('click', () => {
    history.pushState({ page_id: 3 }, '', '/articles')
  })

  nav4.addEventListener('click', () => {
    history.pushState({ page_id: 4 }, '', '/chat')
  })

  const routes = [
    {
      path: '/',
      name: 'Home',
      component: '<div>首页-Home</div>'
    },
    {
      path: '/news',
      name: 'News',
      component: '<div>新闻-News</div>'
    },
    {
      path: '/articles',
      name: 'Articles',
      component: '<div>文章-Artices</div>'
    },
    {
      path: '/chat',
      name: 'Chat',
      component: '<div>聊天-Chat</div>'
    }
  ]

  function mountComponent(path) {
    const currentRoute = routes.find(route => route.path === path)
    const route = currentRoute ?? routes[0]
    document.querySelector('#content').innerHTML = route.component
  }


  window.addEventListener('pushState', e => {
    // 监听pushState自定义事件,根据参数做出对应的页面挂载
    const [state, unused, url] = e.arguments
    console.log(url);
    mountComponent(url)
  })
重点

hash模式是不需要后端服务配合的。但是history模式下,如果你再跳转路由后再次刷新会得到404的错误,这个错误说白了就是浏览器会把整个地址当成一个可访问的静态资源路径进行访问,然后服务端并没有这个文件 (回答错误(╥﹏╥),-10分)

没刷新时,只是通过pushState改变URL,不刷新页面

http://127.0.0.1:5500/ ==》 http://127.0.0.1:5500/index.html // 默认访问路径下的index.html文件,没问题
http://127.0.0.1:5500/home ==》 http://127.0.0.1:5500/index.html // 仍然访问路径下的index.html文件,没问题
...
http://127.0.0.1:5500/chat ==》 http://127.0.0.1:5500/index.html // 所有的路由都是访问路径下的index.html,没问题

一旦在某个路由下刷新页面的时候,想当于去该路径下寻找可访问的静态资源index.html,无果报错

http://192.168.30.161:5500/mine === http://192.168.30.161:5500/mine/index.html文件,出问题了,服务器上并没有这个资源,404😭

所以一般情况下,我们都需要配置下nginx,告诉服务器,当我们访问的路径资源不存在的时候,默认指向静态资源index.html

location / {
  try_files $uri $uri/ /index.html;
}

总结

  • 一般路由实现主要有historyhash两种方式
  • hash的实现全部在前端,不需要后端服务器配合,兼容性好,主要是通过监听hashchange事件,处理前端业务逻辑
  • history的实现,需要服务器做以下简单的配置,通过监听pushState及replaceState事件,处理前端业务逻辑

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1711663.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

配餐中的红酒温度控制与口感体验

在红酒配餐中&#xff0c;温度控制是影响口感体验的重要因素之一。合适的温度可以释放红酒的香气和风味&#xff0c;使酒体更加圆润和丰富。云仓酒庄雷盛红酒以其卓着的品质和与众不同的口感&#xff0c;成为了红酒爱好者们的首要选择品牌。下面将介绍如何通过温度控制提升红酒…

奈飞CEO最新访谈:抢走你饭碗的不是AI,而是能熟练使用AI的人

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;所以创建了“AI信息Gap”这个公众号&#xff0c;专注于分享AI全维度知识…

【windows】Total Uninstall:一款功能强大的完全卸载软件

软件介绍 Total Uninstall是一款专业的软件卸载工具&#xff0c;旨在帮助用户彻底地清除计算机上的应用程序&#xff0c;包括与应用程序相关的所有文件和注册表项。以下是Total Uninstall的一些主要功能和特点&#xff1a; 完全卸载&#xff1a;软件可以监视应用程序的安装过程…

nodejs版本管理切换工具nvm介绍、nvm下载、nvm安装、配置及nvm使用

最近很多同学问&#xff0c;在工作中&#xff0c;同时在进行2个或者多个不同的项目开发&#xff0c;每个项目的需求不同&#xff0c;进而不同项目必须依赖不同版本的NodeJS运行环境&#xff0c;这种情况下&#xff0c;对于维护多个版本的node将会是一件非常麻烦的事情&#xff…

TypeScript-类型断言

类型断言 当开发者比TS本身更清楚当前的类型是什么&#xff0c;可以使用断言(as)让类型更加精确和具体 const _link document.getElementById(link) console.log(_link.href) // 出错了&#xff0c;如下图 const _link document.getElementById(link) as HTMLAnchorElement…

JVM的相关知识

目录 JVM内存划分 类加载过程 类加载中的“双亲委派模型” JVM内存划分 JVM也就是java进程。这个进程一旦跑起来之后&#xff0c;就会从操作系统里&#xff0c;申请一大块内存空间。JVM接下来就要进一步的对这个大的空间进行划分。划分成不同区域&#xff0c;从而每个区域都…

惯性测量单元M-G370系列广泛用于工业系统各个领域

爱普生现已推出型号为M-G370系列的高稳定性、高精度及极小尺寸封装的惯性测量单元(IMU)&#xff0c;可广泛应用于工业系统的各个领域。 为了节省PCB的面积和产品空间&#xff0c;M-G370系列性测量单元设计精巧&#xff0c;且具有6个自由度:三轴角速率和三轴线性加速度&…

如何使用git上传linux下的项目!---附带每一步截图

在实际项目中&#xff0c;我们需要把自己的模块递给GitHub&#xff0c;需要别人的模块的时候拉下来&#xff0c;那么我们怎么把自己的项目递给GitHub呢&#xff1f;下面做一个总结&#xff1a; 登录GitHub 创建一个仓库 填写相关信息 项目名称是必填的&#xff0c;项目描述可以…

RK3568平台(camera篇)V4L2查询获取设置设备

一.查询设备能力VIDIOC_QUERYCAP struct v4l2_capability cap; ioctl(fd, VIDIOC_QUERYCAP, &cap) struct v4l2_capability 结构体描述了视频采集设备的 driver 信息。 struct v4l2_capability { __u8 driver[16]; // 驱动名字 __u8 card[32]; // 设备名字 __u8 bus_inf…

CSS学习笔记:Less

什么是Less&#xff1f; Less是一个CSS预处理器&#xff0c; Less文件后缀是.less 扩充了CSS 语言&#xff0c;使CSS具备一定的逻辑性、计算能力 可以通俗地理解&#xff1a;Less是一种更好用的CSS 注释 运算 嵌套 Less嵌套的作用&#xff1a;快速生成后代选择器 变量 问…

【Spring MVC】_SpringMVC项目返回数据

目录 1. 注解使用示例 1.1 使用Controller注解 1.2 使用RestController注解 1.3 使用Controller与ResponseBody注解 2. 关于ResponseBody注解 前文已经介绍过使用Controller注解向前端返回一个HTML页面&#xff0c;接下来将介绍向前端返回数据。 关于Controller和RestCon…

Rohm公司参展欧洲PCI盛会

​德国历史悠久的文化名城纽伦堡&#xff0c;即将迎来一场科技盛宴——欧洲PCI展览会。在这个为期三天的盛会中&#xff08;6月11日至13日&#xff09;&#xff0c;Rohm公司将以璀璨之姿&#xff0c;特别聚焦宽带隙&#xff08;WBG&#xff09;设备的璀璨光芒。 此次&#xff0…

正则表达式介绍及一些实例(js语法)

一、正则表达式 正则表达式&#xff0c;全称“Regular Expression”&#xff0c;在代码中常简写为regex、regexp或RE。正则表达式&#xff0c;就是用某种模式去匹配一类字符串的公式。 1. 显式定义&#xff08;构造函数&#xff09; let 变量名 new RegExp("正则表达式…

CVPR2024《RMT: Retentive Networks Meet Vision Transformers》论文阅读笔记

论文链接&#xff1a;https://arxiv.org/pdf/2309.11523 代码链接&#xff1a;https://github.com/qhfan/RMT 引言 ViT近年来在计算机视觉领域受到了越来越多的关注。然而&#xff0c;作为ViT的核心模块--自注意力缺乏空间先验知识。此外&#xff0c;自注意力的二次计算复杂度…

oracle 12c GI卸载流程

集群节点停止服务 [crsctl stop crs -f grid运行deinstall [rootprimary1 bin]# su - grid [gridprimary1 ~]$ cd $ORACLE_HOME/deinstall [gridprimary1 deinstall]$ ls bootstrap_files.lst bootstrap.pl deinstall deinstall.pl deinstall.xml jlib readme.txt …

电机控制系列模块解析(25)—— 过压抑制与欠压抑制

一、概念解析 变频器作为一种重要的电机驱动装置&#xff0c;其内置的保护功能对于确保系统安全、稳定运行至关重要。以下是关于变频器过压抑制、欠压抑制&#xff08;晃电抑制&#xff09;、发电功率限制、电动功率限制等保护功能的详细说明&#xff1a; 过压抑制 过压抑制是…

基于python flask的疾病数据采集与可视化大屏,实现关联规则算法的治疗方法分析

背景 基于Python Flask的疾病数据采集与可视化大屏&#xff0c;旨在实现对疾病数据的采集、分析和可视化展示&#xff0c;为医疗领域提供决策支持和治疗方法分析。其中&#xff0c;关联规则算法被应用于治疗方法分析&#xff0c;旨在发现不同治疗方式之间的关联性和规律性&…

【RuoYi】使用代码生成器完成CRUD操作

一、前言 前面&#xff0c;介绍了如何下载和启动我们的RuoYi框架。为了让小伙伴们认识到ruoyi的强大&#xff0c;那么这篇博客就介绍一下如何使用ruoyi的代码生成器&#xff0c;自动生成前端页面以及后端的对应数据库表的CRUD操作&#xff01;&#xff01;&#xff01;真的很强…

填补领域空白!TerDiT:首次探索大规模DiT模型量化问题(MMLab出品)

论文链接&#xff1a;https://arxiv.org/pdf/2405.14854 项目链接&#xff1a;https://github.com/Lucky-Lance/TerDiT 最近在大规模预训练的文本到图像扩散模型方面的发展显著提高了高保真图像的生成能力&#xff0c;特别是基于transformer架构的扩散模型&#xff08;DiTs&a…

linux中使用gdb调试c++的dump文件

1 查看系统是否开启dump生成 0表示没开启 ulimit -c 但是这个只是针对当前这个连接&#xff0c;如果想要永久修改可以修改配置文件&#xff1a;vim /etc/profile&#xff0c;然后添加上面的命令ulimit - c unlimited.然后执行source /etc/profile或者重启使刚刚的配置可以…