前端路由如何修改 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 去捣鼓一个轮子出来,但是没必要,别人造的库,嵌套的那叫一个眼花缭乱,各式各样都给你考虑好了,所以说 珍爱生命,远离造轮

相关文章
|
20天前
|
前端开发
[牛客网-前端大挑战QD2] 获取url参数
[牛客网-前端大挑战QD2] 获取url参数
23 0
|
20天前
|
移动开发 前端开发 API
深入理解前端路由:构建现代 Web 应用的基石(上)
深入理解前端路由:构建现代 Web 应用的基石(上)
深入理解前端路由:构建现代 Web 应用的基石(上)
|
20天前
|
前端开发 搜索推荐 UED
解密前端路由: hash模式vs.history模式
解密前端路由: hash模式vs.history模式
|
20天前
|
移动开发 前端开发 JavaScript
前端vue2、vue3去掉url路由“ # ”号——nginx配置(一)
前端vue2、vue3去掉url路由“ # ”号——nginx配置
73 0
|
20天前
|
前端开发 JavaScript 应用服务中间件
前端vue2、vue3去掉url路由“ # ”号——nginx配置(二)
前端vue2、vue3去掉url路由“ # ”号——nginx配置
75 0
|
11天前
|
前端开发 网络架构
1天搞定SpringBoot+Vue全栈开发 (8)前端路由VueRouter(进行组件切换)
1天搞定SpringBoot+Vue全栈开发 (8)前端路由VueRouter(进行组件切换)
|
19天前
|
前端开发
前端路由机制实现hash-history
前端路由机制实现hash-history
23 1
|
20天前
|
缓存 前端开发 JavaScript
【JavaScript 技术专栏】JavaScript 前端路由实现原理
【4月更文挑战第30天】本文探讨了JavaScript前端路由在SPA中的重要性,阐述了其基本原理和实现方式,包括Hash路由和History路由。前端路由通过监听URL变化、匹配规则来动态切换内容,提升用户体验和交互性。同时,文章也提到了面临的SEO和页面缓存挑战,并通过电商应用案例分析实际应用。理解并掌握前端路由能助开发者打造更流畅的单页应用。
|
20天前
|
前端开发 开发者 iOS开发
【Flutter前端技术开发专栏】Flutter中的路由管理与页面跳转
【4月更文挑战第30天】本文介绍了Flutter的路由管理与页面跳转,包括基本和命名路由管理。基本路由使用`Navigator`的`push`和`pop`方法,如`MaterialPageRoute`和`CupertinoPageRoute`。命名路由则通过路由表注册名称进行跳转,如`Navigator.pushNamed`。此外,还展示了如何通过构造函数、`arguments`和`PageRouteBuilder`进行路由传值。掌握这些知识能提升Flutter开发效率。
【Flutter前端技术开发专栏】Flutter中的路由管理与页面跳转
|
20天前
|
前端开发 JavaScript 数据可视化
前端vite+vue3——自动化配置路由布局
前端vite+vue3——自动化配置路由布局
37 0