vue-router 的不同的历史记录模式详解

简介: 前几天面试官问我能不能讲一下 vue-router 两种模式,好家伙不就是 #/path 和 /path 这还用问?面试官浅笑一下,呵呵,讲得有点表面,深层的地方没有讲出来

背景介绍

前几天面试官问我能不能讲一下 vue-router 两种模式,好家伙不就是 #/path/path 这还用问?面试官浅笑一下,呵呵,讲得有点表面,深层的地方没有讲出来,好家伙,vue-router 算是埋伏了我一手,所以这篇文章将会带你把 vue-router 这两种模式的底裤扒出来看看

拿来把你

有什么用?

前面说过,我印象最深的就是 vue-router 的 Hash 模式,它在切换的时候根目录后面是会跟着一个 # 字符,那么为什么选中了它呢?

首先讲一下 # 的含义

  1. 代表位置

# 可以作为一个锚点标记元素的位置,和 <a> 元素搭配可以起到跳转的作用

<a href="#xxx">跳转到到 xxx 位置</a>
<!-- ....假设中间有很多元素.... -->
<div id="xxx"></div>

这个作用是不是很符合路由或者说我们平时点击网页时那种导航的作用呢?

  1. 浏览器 url '不认' #

# 不是浏览器 url 的亲儿子

  1. 发送 http 请求时,# 及其后面的字符 '不认',不包含在请求参数,也不算请求 URL,不会发送到服务器
  2. 在浏览器中当前页面的 URL 输入栏中,随意打上 # + 任意字符并回车,不会触发重载页面,因为浏览器会认为你想要跳转到当前页面某个位置
  3. google spider(谷歌的爬虫)会忽视掉 # 后面的内容,也就是采取这种方式的内容型网站的路由对应的页面内容不会被谷歌的浏览引擎检索到并推荐到搜索内容中(你嫌爬不到我还不想给呢),这也就是为什么 vue-router 4 中说 Hash 模式对 SEO 有不好的影响

你是故意找茬是不是?

那么 # 在 vue-router 中是干什么的?

前面主要说了 # 可以用来标记锚点元素,而浏览器也提供了 onhashchange 的事件,可以帮助我们监听路由的变化,从而和浏览器的前进后退动作联系在一起,这里的实现先不讲,在后面中与 history 的区别中会详细介绍

所以 vue-router 的 hash 模式的 # 字符并不是空穴来风,而是源于浏览器提供的原生 API

hash 和 history 两种模式的区别

既然是详解,那肯定的扒源码来看看,vue-router 选择不同的模式的时候是怎么处理的呢?(以 vue-router 3 和 4 版本比较)

首先是 vue-router3

export default class VueRouter {
  ...
  constructor (options: RouterOptions = {}) {
    ...
    switch (mode) {
      case 'history':
        this.history = new HTML5History(this, options.base)
        break
      case 'hash':
        this.history = new HashHistory(this, options.base, this.fallback)
        break
      case 'abstract':
        this.history = new AbstractHistory(this, options.base)
        break
      default:
        if (process.env.NODE_ENV !== 'production') {
          assert(false, `invalid mode: ${mode}`)
        }
    }
  }
  ...
}

可以看到,history 模式和 hash 模式是和 HTML5History 和 HashHistory 两个对象相关的,HTML5History 用的是 history 提供的 pushState 和 replaceState 两个方法,HashHistory 用的是 hashchange 事件监听,你们可以猜一下 vue-router4 的 hash 模式是怎么实现的

// 摊牌了,我不装了,其实我是 history 模式套壳
import { createWebHistory } from './html5'
...
export function createWebHashHistory(base?: string): RouterHistory {
  ...
  return createWebHistory(base)
}

摊牌了不装了

鉴于 vue-router4 对于 hash 模式直接开摆,因此只会浅尝一下 vue-router3 的源码

无论是 hash 还是 history 模式,都需要实现路由的功能,即下面两个核心

  1. 改变 URL 却不引起刷新
  2. 检测 URL 的变化, URL 变化的方式有以下几种

    1. 浏览器前进后退
    2. a 标签超链接改变
    3. window.location 改变

改变 URL 但不会引起刷新

hash

对于第一个,前面说过 hash 模式采用 # + path 的方式,改变了 URL 但是浏览器不会刷新重载

在代码层面就是 window.location.hash 的修改

// vue-router/src/history/hash.js 139
function pushHash(path) {
  if (supportsPushState) {
    pushState(getUrl(path));
  } else {
    window.location.hash = path;
  }
}

history

调用 history 中的 pushState 方法以及 replaceState 方法,也不会引起页面刷新

history.pushState(state, title[, url])

具体参数详情参考 MDN 文档的介绍,友情链接

// 跳转到登录页
history.pushState(null, "", "login");

检测 URL 的变化

hash

hash 监听的是 hashchange 事件,在 vue-router/src/history/hash.js 49 行的 setupListeners 方法中,设置了监听器

export class HashHistory extends History {
  ...
  setupListeners () {
    const eventType = supportsPushState ? 'popstate' : 'hashchange'
    window.addEventListener(
      eventType,
      handleRoutingEvent
    )
  ...
  }
}

history

不用说你都应该知道,就是上面源码中的 popstate 事件

因此在检测 URL 的变化的策略上,hash 是监听的 hashchange 事件,而 history 监听的是 popstate 事件

改变 URL 的方法

window.location

hash 会修改 window.location.hash 来达到修改 URL 的目的,而 history 不会

a 标签

<router-link> 里面其实就是 <a>(默认的情况下),hash 模式下,直接点击跳转就行了,因为有 # 锚点标记,但是 history 要阻止跳转,不然就会想服务器发送请求了,拦截后,调用 pushState/replaceState

浏览器前进后退

浏览器前进后退其实 history.go,back,forward 的调用,两种模式都是可以正常使用的,因为无论是 hash 还是 history 都会向历史记录栈中存入记录

总结

看了 vue-router 中关于 hash 和 history 部分,基本就可以了解 vue-router 的运作原理了,对于 hash 和 history,hash 是 2014 年前采用的方法,history 是 HTML5 的新 API,以后应该都是采用 history 的比较多,这一点可以参考 vue-router4 的 hash 模式,直接开摆了属于是

好家伙我他妈直接好家伙

参考资料

vue-router hash 模式

一看就懂的 router 路由实现原理

相关文章
|
6月前
|
JavaScript 索引 容器
vue实现多文件预览以及切换
vue实现多文件预览以及切换
114 0
|
JavaScript
|
4月前
|
JavaScript
vue解决方案 | router-view路由切换时页面不刷新
vue解决方案 | router-view路由切换时页面不刷新
243 0
|
6月前
|
JavaScript
Vue 路由切换时页面刷新页面重新渲染
在Vue中,路由切换并不自动重新渲染页面,这可能导致传递参数到子组件时出现问题。使用`this.$route.replace(&#39;地址&#39;)`或`this.$route.push({name:&#39;地址&#39;,params:{key:value}})`进行跳转,但子组件不会响应变化。为解决此问题,需监听`$route`对象的变化,如图所示,通过`watch: {$route}`确保页面更新。
397 5
|
6月前
|
JavaScript
vue 页面刷新、重置、更新页面所有数据
vue 页面刷新、重置、更新页面所有数据
|
JavaScript 网络架构
vue | 动态路由刷新空白
解决在vue3中添加动态路由后,刷新页面空白,并且提示没有正确的路径。
270 0
vue | 动态路由刷新空白
|
JavaScript 应用服务中间件 nginx
vue中路由history模式下的404问题
vue中路由history模式下的404问题
66 1
|
11月前
|
JavaScript
解决Vue的history模式刷新页面出现404的问题
解决Vue的history模式刷新页面出现404的问题
|
11月前
|
JavaScript
Vue路由 replace属性 控制浏览记录不能前进或后退
Vue路由 replace属性 控制浏览记录不能前进或后退
|
JavaScript 容器
vue项目切换页面会白屏,刷新之后才会正常显示(已解决)
vue项目切换页面会白屏,刷新之后才会正常显示(已解决)
617 0