前端路由如何修改 URL ?

简介: 前端路由需要实现两个核心, 1. 修改 URL 而不引起页面刷新, 2. 检测 URL 的变化, 这两个核心取决于你采用的前端路由技术选择的方案, 即 hash 和 history, 当选择了技术后,

前端路由需要实现两个核心, 1. 修改 URL 而不引起页面刷新, 2. 检测 URL 的变化, 这两个核心取决于你采用的前端路由技术选择的方案, 即 hash 和 history, 当选择了技术后, 前端路由是怎样去实现修改的操作的呢?

前情提要

本篇文章以 vue-router 为例, 并对其中的 API 源码进行一定的分析, 并且只会介绍 history 模式, 前端路由修改 URL 的方式, 原因是 hash 是 2014 年以前普遍采用的方式, history 模式是 HTML5 标准的新方式, 而且在 vue-router4 中 hash 模式只是作为 history 模式的降级处理(当 history 模式无法修改时, 会采用 hash 模式的修改), 关于两种模式更详细的区别和介绍可以参考另一篇文章, vue-router 的不同的历史记录模式详解

改变 URL 的方式

在原生浏览器提供的 API 中,我们可以通过以下几种方式修改 URL

  1. 浏览器前进后退
  2. a 标签
  3. window.location

而在 vue-router4 中, Router 实例提供的修改 URL 或者说路由的导航方式有以下几种

  1. push
  2. replace
  3. go
  4. back
  5. forward

接下来会对上述方法对应的源码进行分析

编程式导航

编程式导航指的是在 vue/JS 中编写导航代码, 前面说了 Router 实例提供了 5 种修改 URL 的方法, 实际在源码中只实现了 3 种, 即 push, replace, go 而对于 back, forward 实际上是 go 的变种, 在 router/src/router.js 1141 的 Router 定义中 back, forward 实际是这样实现的, 把代码复用发挥到了极致了属于是

const router: Router = {
  ...
  push,
  replace,
  go,
  back: () => go(-1),
  forward: () => go(1),
  ...
};

真有你的

go

go/back/forward 有点接近浏览器前进后退的语义,那 go 的实现究竟和浏览器前进后退有没有关系呢?浏览器的前进后退对应的是浏览器的两个 API, window.history.back() 和 window.history.forward(), 而 vue-router4 中 go 的定义实际上也明说了

  1. window.history.back() == myHistory.go(-1)
  2. window.history.forward() == myHistory.go(1)

所以 go 的实现猜也猜得到了,它的实现在 router/src/history/html5.ts 320 (这里提一嘴 vs code 的代码导航的功能, 我查看 go 的实现的时候,它直接就给我跳了,实际上 go 的实现有多个,没有给我选择的空间,这里我手动流汗黄豆)

function go(delta: number, triggerListeners = true) {
  if (!triggerListeners) historyListeners.pauseListeners();
  history.go(delta);
}

直接是使用的浏览器管理历史记录的对象, history.go(), 我有点怀疑其实浏览器原生实现 history.back() 和 history.forward() 也是用的 history.go 了, 注意实现中的 triggerListeners, 它将决定你的操作是否触发前端路由的监听器, 注意,小细节,前端路由是需要检测 URL 变化的,而 history 模式 是通过 popState 事件实现的检测变化, window.history.go(), window.history.back(), window.history.forward() 调用的时候可不会触发 popState 事件

又是一个小细节

replace

replace 实际上是调用的 replaceState(), 这个东西可能就需要 MDN 的文档来了解一下了, 友情链接, 注意一个小细节, 它名字叫做 replaceState, 但实际上它和 pushState 一样都会在浏览器历史纪录上创建一个新的历史记录项, 比如你在当前页面的控制台(你用手机看的话当我没说)调用下面这个代码

history.pushState(null, "", "d");
history.replaceState(null, "", "a");

虽然传递的 URL 非常离谱, 但是不妨碍它能够无刷运行, 它是不会发出请求的, 而且最重要的是, 它会新增两条历史记录, 假设你在 https://www.baidu.com 调用的这个, 那么你看历史记录里面其实会有两条记录, 一条 https://www.baidu.com/a 一条 https://www.baidu.com/b

除此之外, 还有一个小细节, 如果你之前没有用过 pushState() 直接上的 replaceState, 那么无论调用多少次 replaceState 都只是修改当前页面的历史记录, 而不会新增

回到 vue-router4 的实现, 在 router/src/history/html5.js 240

function replace(to: HistoryLocation, data?: HistoryState) {
  const state: StateEntry = assign(
    {},
    history.state,
    buildState(
      historyState.value.back,
      // keep back and forward entries but override current position
      to,
      historyState.value.forward,
      true
    ),
    data,
    { position: historyState.value.position }
  );

  changeLocation(to, state, true);
  currentLocation.value = to;
}

这里的 replace 做了三件事

  1. 创建状态对象, 这是 vue-router 自己的状态对象, 这个对象包含 window.history 自己的 state, 还有上一个历史记录和前一个历史记录, 以及编码时需要传递的数据
  2. 修改浏览器历史记录, 涉及到 replaceState() 的 changeLocation(), changeLocation() 很重要, push, replace 和浏览器自身的历史记录关联起来就靠它
  3. 修改 Router 实例中 URL 的值

重点分析一下 changeLocation(), 在 router/src/history/html5.js 203

function changeLocation(
    to: HistoryLocation,
    state: StateEntry,
    replace: boolean
  ): void {
    ...
    try {
      history[replace ? 'replaceState' : 'pushState'](state, '', url)
      historyState.value = state
    } catch (err) {
      ...
      location[replace ? 'replace' : 'assign'](url)
    }
  }

从这里可以看到 vue-router 里面 push 和 replace 实际上都是调用 changeLocation 修改浏览器历史记录的,而且当出现错误的时候会降级使用, window.location 的方式修改 URL (Hash 模式用的就是 window.location)

push

push 常用于栈之类的数据结构, 在 vue-router 里实际就是压入浏览器历史记录栈中的, 而在 vue-router 的源码实现中, push 和 replace 的实现是高度一致的,这也就是为什么先讲了 replace, 简简单单看一下源码

  function push(to: HistoryLocation, data?: HistoryState) {
    ...
    const currentState = assign(
      {},
      historyState.value,
      history.state as Partial<StateEntry> | null,
      {
        forward: to,
        scroll: computeScrollPosition(),
      }
    )

    changeLocation(currentState.current, currentState, true)

    const state: StateEntry = assign(
      {},
      buildState(currentLocation.value, to, null),
      { position: currentState.position + 1 },
      data
    )

    changeLocation(to, state, false)
    currentLocation.value = to
  }

和 replace 实现的目的实际上是一样的, 但是有个小细节, 它是调用了两次 changeLocation, 又一个小细节!第一次是为了更新当前页面的历史记录状态对象信息, 更新信息包括当前页面滚动信息(当我们返回上一级路由时能够直接闪现到上一次访问时的位置), 以及 forward(前一个历史记录 URL), 因为这个页面要跳转到新的页面, 第二次 changeLocation 就是创建跳转页面的历史记录状态对象了

总结

前端路由(指 vue-router), 它们修改 URL 并不是说修改了就算了, 直接修改调用 replaceState 和 pushState 就行了, vue-router 在修改路由的同时, 用来大量的精力去维护状态对象, 状态对象中存储了一个历史记录条目的信息(小细节, 这个状态对象序列化后的大小不能超过 640kb), 这个状态对象是前端路由传递信息, 维护状态, 正确导航的核心

看来 vue-router 部分源码后,其实它们的很多实现也是依据浏览器提供的原生 API 实现的,并不是毫无根据的,说白了,你自己也能用这个 API 去捣鼓一个轮子出来,但是没必要,别人造的库,嵌套的那叫一个眼花缭乱,各式各样都给你考虑好了,所以说 珍爱生命,远离造轮

相关文章
|
2月前
|
JavaScript 前端开发 开发者
Vue.js 框架大揭秘:响应式系统、组件化与路由管理,震撼你的前端世界!
【8月更文挑战第27天】Vue.js是一款备受欢迎的前端JavaScript框架,以简洁、灵活和高效著称。本文将从三个方面深入探讨Vue.js:响应式系统、组件化及路由管理。响应式系统为Vue.js的核心特性,能自动追踪数据变动并更新视图。例如,通过简单示例代码展示其响应式特性:`{{ message }}`,当`message`值改变,页面随之自动更新。此外,Vue.js支持组件化设计,允许将复杂界面拆分为独立且可复用的组件,提高代码可维护性和扩展性。如创建一个包含标题与内容的简单组件,并在其他页面中重复利用。
57 3
|
2月前
|
Python
路由(URL routing)
【8月更文挑战第23天】
29 4
|
3天前
|
存储 JavaScript 前端开发
前端 vue:路由的高级使用
前端 vue:路由的高级使用
|
3天前
|
资源调度 JavaScript 前端开发
前端vue:路由的基本使用
前端vue:路由的基本使用
|
5天前
|
移动开发 前端开发 JavaScript
浅谈前端路由原理hash和history
该文章详细解析了前端路由的两种模式——Hash模式与History模式的工作原理及其实现方式,并通过实例代码展示了如何在实际项目中运用这两种路由模式。
|
13天前
|
移动开发 缓存 前端开发
构建高效的前端路由系统:从原理到实践
在现代Web开发中,前端路由系统已成为构建单页面应用(SPA)不可或缺的核心技术之一。不同于传统服务器渲染的多页面应用,SPA通过前端路由技术实现了页面的局部刷新与无缝导航,极大地提升了用户体验。本文将深入剖析前端路由的工作原理,包括Hash模式与History模式的实现差异,并通过实战演示如何在Vue.js框架中构建一个高效、可维护的前端路由系统。我们还将探讨如何优化路由加载性能,确保应用在不同网络环境下的流畅运行。本文不仅适合前端开发者深入了解前端路由的奥秘,也为后端转前端或初学者提供了从零到一的实战指南。
|
7天前
|
前端开发 JavaScript
前端JS截取url上的参数
文章介绍了两种前端JS获取URL参数的方法:手动截取封装和使用URLSearchParams。
20 0
|
2月前
|
API 开发者 Python
"FastAPI路由大揭秘!轻松玩转URL映射,让你的Web应用路由设计既RESTful又灵活多变,秒杀传统框架的秘籍在这里!"
【8月更文挑战第31天】在Web开发中,路由是连接用户请求与后端逻辑的关键。FastAPI作为现代Python Web框架的佼佼者,以其简洁的API设计和高性能,提供了高度灵活的路由系统。本文通过开发一个博客系统的案例,详细介绍了FastAPI中路由的实现方法,包括基础路由定义、参数类型验证及路由分组与嵌套等,展示了如何轻松构建RESTful风格的URL映射,提升应用的可维护性和扩展性。
47 2
|
2月前
|
开发者 Java UED
大文件传输不再头疼:揭秘Struts 2如何轻松应对文件上传与下载难题!
【8月更文挑战第31天】在Web应用开发中,文件上传与下载至关重要。Struts 2作为主流Java EE框架,凭借Commons FileUpload及文件上传拦截器简化了相关操作。本文探讨Struts 2在文件传输上的优势,通过具体配置与代码示例,展示如何设置最大文件大小、使用`fileUpload`拦截器以及实现文件上传与下载功能。对于大文件传输,Struts 2不仅能够轻松应对,还支持上传进度显示,有效提升了用户体验。总体而言,Struts 2为文件传输提供了高效便捷的解决方案,助力开发者构建稳定可靠的Web应用。然而,在处理大文件时需兼顾网络带宽与服务器性能,确保传输顺畅。
42 0
|
2月前
|
API UED 开发者
Vaadin路由魔法:导航之舟,带你穿越页面迷宫!驾驭神奇URL,解锁无限可能!
【8月更文挑战第31天】Vaadin是一款现代Java Web开发框架,其路由机制结合前后端路由,确保流畅的用户体验和高效服务器资源利用。通过`@Route`注解和`Router`类,开发者可以轻松定义和管理页面路径。例如,`@Route(&quot;home&quot;)`可指定视图路径,而参数化路由如`@Route(&quot;user/:userId&quot;)`则允许URL传参。此外,Vaadin还提供了丰富的导航API和自定义路由事件监听器,助力开发者构建结构清晰且体验优秀的Web应用。
31 0
下一篇
无影云桌面