前端路由 Hash 模式和 History 模式

在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事件,处理前端业务逻辑

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

uart_tty_驱动程序框架

UART子系统(四) TTY驱动程序框架_tty驱动框架-CSDN博客

【网络层】ICMP 因特网控制协议

文章目录 ICMP 含义以及作用ICMP协议解析结合ICMP协议和ping常见问题 ICMP 含义以及作用 ICMP&#xff1a;Internet control massage protocol 因特网控制协议 Internet控制报文协议ICMP是网络层的一个重要协议。 ICMP协议用来在网络设备间传递各种差错和控制信息&#xff0c;…

【优选算法】分治 {三分快排:三指针优化,随机选key,快速选择算法;归并排序:统计数组中的逆序对,统计数组中的翻转对;相关编程题解析}

一、经验总结 1.1 三分快排 优化一&#xff1a;三指针优化 之前学习的快速排序无法妥善处理相等或重复序列的排序问题&#xff08;有序且三数取中无效&#xff09;&#xff0c;使快速排序的效率无法达到最优。 为了解决重复序列的问题&#xff0c;我们将原先的双指针法&…

java项目之智能家居系统源码(springboot+vue+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的智能家居系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 基于Springboot的智能家居系…

Redis:redis基础

Redis Remote Dictionary Service即远程字典服务 一个基于内存的key-value结构数据库,在开发中常常作为缓存存储不经常被改变的数据 基于内存存储,读写性能高 在企业中应用广泛 Redis介绍 用C语言开发的开源高性能键值对数据库,可以达到10w的qps,可以存储丰富的value类型…

IDEA中各种Maven相关问题(文件飘红、下载依赖和启动报错)

错误情况 包名、类名显示红色、红色波浪线&#xff0c;大量依赖提示不存在&#xff08;程序包xxx不存在&#xff09; 工程无法启动 一、前提条件 1、使用英文原版IDEA 汉化版的可能有各种奇怪的问题。建议用IDEA英文版&#xff0c;卸载重装。 2、下载maven&#xff0c;配置环…

评测 香橙派OrangePi在智能交通上的应用

1、OrangePi应用场景 关于 Orange Pi AI Pro 开发板是香橙派联合华为精心打造的高性能 AI 开发板&#xff0c;其搭载了昇腾 AI 处理器&#xff0c;可提供 8TOPS INT8 的计算能力&#xff0c;内存提供了 8GB 和 16GB两种版本。可以实现图像、视频等多种数据分析与推理计算&#…

12-常用类

1. 包装类 针对八种基本数据类型封装的相应的引用类型。 有了类的特点&#xff0c;就可以调用类中的方法。&#xff08;为什么要封装&#xff09; 基本数据类型包装类booleanBooleanchar CharacterbyteByteshortShortintIntegerlongLongfloatFloatdoubleDouble 1.1 …

seRsync + Rsync 实时同步

1&#xff0c;结构图 2&#xff0c;节点A 2.1 安装rsync yum install -y rsync2.2 安装seRsync 下载这个压缩包sersync2.5.4_64bit_binary_stable_final.tar.gz 解压后&#xff0c;将sersync2复制到系统可执行程序路径&#xff1a;/usr/local/bin/&#xff1b;创建sersync配…

Visual Studio 的使用

目录 1. 引言 2. 安装和配置 2.1 系统要求 2.2 安装步骤 2.3 初次配置 3. 界面介绍 3.1 菜单栏和工具栏 3.2 解决方案资源管理器 3.3 编辑器窗口 3.4 输出窗口 3.5 错误列表 3.6 属性窗口 4. 项目管理 4.1 创建新项目 4.2 导入现有项目 4.3 项目属性配置 5. 代…

SpringSecurity6从入门到实战之SpringSecurity快速入门

SpringSecurity6从入门到实战之SpringSecurity快速入门 环境准备 依赖版本号springsecurity6.0.8springboot3.0.12JDK17 这里尽量与我依赖一致,免得在学习过程中出现位置的bug等 创建工程 这里直接选择springboot初始化快速搭建工程,导入对应的jdk17进行创建 直接勾选一个web…

QtCreator调试运行工程报错,无法找到相关库的的解决方案

最新在使用国产化平台做qt应用开发时&#xff0c;总是遇到qtcreator内调试运行 找不到动态库的问题&#xff0c;为什么会出现这种问题呢&#xff1f;明明编译的时候能够正常通过&#xff0c;运行或者调试的时候找不到相关的库呢&#xff1f;先说结论&#xff0c;排除库本身的问…

计算机网络7——网络安全1 概述与加密

文章目录 一、网络安全问题概述1、计算机网络面临的安全性威胁2、安全的计算机网络3、数据加密模型 二、两类密码体制1、对称密钥密码体制2、公钥密码体制 随着计算机网络的发展&#xff0c;网络中的安全问题也日趋严重。当网络的用户来自社会各个阶层与部门时&#xff0c;大量…

Raven2掠夺者2渡鸦2账号怎么验证 注册怎么验证账号教程

《渡鸦2》作为韩国孕育的次世代MMORPG手游巨制&#xff0c;是《Raven》系列辉煌传奇的最新篇章&#xff0c;它在暗黑奇幻的广袤天地间再度挥洒创意&#xff0c;深度融合前所未有的游戏机制与海量新颖内容&#xff0c;为该类型游戏树立了崭新的里程碑。公测日期锁定在2024年5月2…

2024 在Pycharm管理数据库

2024 在Pycharm管理数据库 Pycharm 社区版DataBase Navigator 数据库管理插件(Plugins)安装使用(sqlite为例添加数据) 文章目录 2024 在Pycharm管理数据库一、Pycharm数据库配置1、Database Navigator插件安装2、连接数据库 二、数据库使用1、插件自带基本操作2、控制台操作 …

LAMP集群分布式实验报告

前景&#xff1a; 1.技术成熟度和稳定性&#xff1a; LAMP架构&#xff08;Linux、Apache、MySQL、PHP&#xff09;自1998年提出以来&#xff0c;经过长时间的发展和完善&#xff0c;已经成为非常成熟和稳定的Web开发平台。其中&#xff0c;Linux操作系统因其高度的灵活性和稳…

恢复视频3个攻略:从不同情况下的恢复方法到实践!

随着科技的进步&#xff0c;我们的生活被各种各样的数字内容所包围&#xff0c;其中&#xff0c;视频因其独特的记录性质&#xff0c;承载着许多重要的资料。但不管是自媒体人还是普通人日常生活随手一拍&#xff0c;都会遇到误删视频的情况。为了帮助您找回手机视频&#xff0…

2024全新升级版家政服务小程序源码 支持家政预约+上门服务+SAAS系统+可二开

随着科技的飞速发展&#xff0c;家政服务行业也迎来了数字化转型的浪潮。为了满足市场日益增长的需求&#xff0c;分享一款2024全新升级版的家政服务小程序源码。该源码不仅支持家政预约和上门服务&#xff0c;还集成了SAAS系统&#xff0c;并支持二次开发&#xff0c;为用户带…

【机器学习300问】103、简单的经典卷积神经网络结构设计成什么样?以LeNet-5为例说明。

一个简单的经典CNN网络结构由&#xff1a;输入层、卷积层、池化层、全连接层和输出层&#xff0c;这五种神经网络层结构组成。它最最经典的实例是LeNet-5&#xff0c;它最早被设计用于手写数字识别任务&#xff0c;包含两个卷积层、两个池化层、几个全连接层&#xff0c;以及最…

企业级OV SSL证书的应用场景和加密手段

为了保护数据传输的安全性与用户隐私&#xff0c;企业级OVSSL&#xff08;Organization Validation SSL&#xff09;证书成为众多企业的首选安全解决方案。本文将深入探讨OVSSL证书的应用场景及其实现数据加密的核心手段&#xff0c;为企业构建坚不可摧的在线信任桥梁提供指南。…