Vue ——Vue-router有几种模式?

Vue-router 有几种模式?如何实现?

  Vue-router 是 vue 框架的路由插件。

  Vue-router 有两种模式:Hash 模式和 History 模式。在开发的时候可以通过使用在路由配置中配置mode这个属性的值来配置使用哪种路由,如果不配置这个字段就默认是 hash 模式。

  * Hash 模式:该模式有一个很明显的标志就是 URL 中带有 #,我们可以通过 window.location.hash 来获取这个值。

  * History 模式:该模式是由 h5 提供的 history 对象实现的。

  我们可以通过源码来看看路由是怎么实现的:

路由模式参数:

  在 Vue-router 中是通过mode 这一个参数来实现控制路由的实现模式的:

const router = new VueRouter({
  mode: 'history',
  // 配置路由
  routes:[...]
})

  在创建路由的实例中,我们通过 mode 参数指定当前创建路由的方式,我们可以通过 VueRouter 类的定义来入手:

export default class VueRouter {

mode: string; // 传入的字符串参数,指示 history 类别
history: HashHistory | HTML5History | AbstractHistory; // 实际起作用的对象属性,必须是以上三个类的枚举
fallback: boolean; // 如浏览器不支持,'history' 模式需回滚为 'hash' 模式

constructor (options: RouterOptions
= {}) {

let mode </span>= options.mode || 'hash' <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 默认为'hash'模式</span>
<span style="color: rgba(0, 0, 255, 1)">this</span>.fallback = mode === 'history' &amp;&amp; !supportsPushState <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 通过supportsPushState判断浏览器是否支持'history'模式</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.fallback) {
  mode </span>= 'hash'<span style="color: rgba(0, 0, 0, 1)">
}
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">inBrowser) {
  mode </span>= 'abstract' <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 不在浏览器环境下运行需强制为'abstract'模式</span>

}
this.mode = mode

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 根据mode确定history实际的类并实例化</span>
<span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)"> (mode) {
  </span><span style="color: rgba(0, 0, 255, 1)">case</span> 'history'<span style="color: rgba(0, 0, 0, 1)">:
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.history = <span style="color: rgba(0, 0, 255, 1)">new</span> HTML5History(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">, options.base)
    </span><span style="color: rgba(0, 0, 255, 1)">break</span>
  <span style="color: rgba(0, 0, 255, 1)">case</span> 'hash'<span style="color: rgba(0, 0, 0, 1)">:
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.history = <span style="color: rgba(0, 0, 255, 1)">new</span> HashHistory(<span style="color: rgba(0, 0, 255, 1)">this</span>, options.base, <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.fallback)
    </span><span style="color: rgba(0, 0, 255, 1)">break</span>
  <span style="color: rgba(0, 0, 255, 1)">case</span> 'abstract'<span style="color: rgba(0, 0, 0, 1)">:
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.history = <span style="color: rgba(0, 0, 255, 1)">new</span> AbstractHistory(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">, options.base)
    </span><span style="color: rgba(0, 0, 255, 1)">break</span>
  <span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">:
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (process.env.NODE_ENV !== 'production'<span style="color: rgba(0, 0, 0, 1)">) {
      assert(</span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">, `invalid mode: ${mode}`)
    }
}

}

init (app: any / Vue component instance /) {

const history </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.history

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 根据history的类别执行相应的初始化操作和监听</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (history <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> HTML5History) {
  history.transitionTo(history.getCurrentLocation())
} </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (history <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> HashHistory) {
  const setupHashListener </span>= () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    history.setupListeners()
  }
  history.transitionTo(
    history.getCurrentLocation(),
    setupHashListener,
    setupHashListener
  )
}

history.listen(route </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
  </span><span style="color: rgba(0, 0, 255, 1)">this</span>.apps.forEach((app) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    app._route </span>=<span style="color: rgba(0, 0, 0, 1)"> route
  })
})

}

// VueRouter 类暴露的以下方法实际是调用具体 history 对象的方法
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
this.history.push(location, onComplete, onAbort)
}

replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
this.history.replace(location, onComplete, onAbort)
}
}

  从上面可以看出:

  * 作为参数传递进入的字符串属性 mode 只是一个标记,用来指示实际作用的对象属性 history 的实现类,两者对应的关系如下:

mode history 的实现类
history HTML5History
hash HashHistory
abstract AbstractHistory

  * 在初始化对应的 history 之前 ,会对 mode 做一些判断:如果浏览器不支持 HTML5History 方式(通过 supportsPushState 来检测),则 mode 模式就强制设置为 Hash 模式,如果当前不是在浏览器中执行,就 mode 模式就强制设置为 abstract 模式。

  * VueRouter 类中共的 onReady(),push(), 方法只是一个代理,它真正的实现是具体 History 对象的应用方法,在 init() 方法中初始化的时候,就根据 history 对象

具体的实现类来执行不同的操作。

  在浏览器环境下的两种模式,分别是在 HTML5History , HashHistory 两个类中实现的。

 

History 模式实现的路由

  History interface浏览器历史记录栈提供的接口,通过back()forword(),go()等方法我们可以读取浏览器历史记录栈的信息,进行个各种跳转操作。

   在 Html5 开始, History interface 提供了两个新的方法:pushState(),replaceState() 是的我们可以对浏览器的历史记录栈进行修改

 window.history.pushState(stateObject,title,url)

 window.history.replaceState(stateObject,title,Url)

  * stateObject:当浏览器跳转到新的状态的时候,将触发 popState 事件,这个事件将携带这个 stateObject  参数的副本.

  * title: 所添记录的标题

  * URL: 所添记录的 URL

  这两个方法都有一个共同的特点就是,当他们修改了浏览器的历史记录栈后,虽然当前的 URL 改变了,但是不会立即发送请求该 URL 的资源,这就为单页面应用前端路由“更新视图当不重新请求页面”提供了基础。

 

Hash 模式实现的路由

  这种模式实现的路由,在通过链接后面添加 “#”+ 路由名字。

  hash 虽然是出现在 url 中,但是它不会被包含在 HTTP 请求里面。它是用来指示浏览器的动作的,对服务端完全是没有作用的,因此,改变 hash 是不会重新加载页面的。

  可以给 hash 的改变添加监听事件:

  window.addEventLIstener("hashchange",funcRef,false);

  每一次改变 hash ,就会在浏览器的访问历史记录中添加一个记录。

  利用 Hash 的以上特点可以实现前端路由的“更新视图但是不重新加载请求页面”的功能了。

两种模式的比较

  在一般的需求场景中,hash 模式和 history 模式是差不多的,但是它们之间的区别主要如下:

  * pushState 设置的新 URL 可以是与当前 URL 同源的任意 URL; 而修改 hash 只能修改 # 符号之后的部分;

  * pushState 设置的新的 URL 可以与当前的 URL 一摸一样,这样也会把记录添加到栈中,而 hash 设置的新值必须与原来的不一样才会被添加到栈中;

  * pushState 通过 stateObject 可以添加任何的数据到记录中;而 hash 只可以添加短字符串;

  * pushState 可以额外设置 title 属性后供后续使用;

 

History 模式的一个问题:

  我们知道对于单页面的应用来说,理想的使用场景是仅仅在进入应用的时候加载 index.html, 后续在的网络操作通过 AJAX 完成,不会根据当前的 URL 重新请求数据,但是难免会遇到特殊情况,如用户直接在地址栏中输入并回车,浏览器重启加载应用程序等。

  hash 模式只会改变 hash 部分的内容,并且 hash 部分的内容是不会包含在 Http 请求里面的,

http://oursite.com/#/user/id  // 只会发送 http://oursite.com/

  在 Hash 模式下根据 url 请求页面时不会出现什么问题的,但是如果使用的是 History 模式的,则会将 URL 修改得就和正常请求后端的 URL 一样。

   http://oursite.com/user/id

  在次情况下向后端发送请求的话,如果后端没有配置对应的 /user/id 的路由处理,就会返会 404 错误。

  如果一定要使用 History 模式的话,为了防止页面刷新出现 404 情况,可以使用官方提供的解决方法是在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何的静态资源,则返回一个同一个 html.index 页面,这个页面就是你应用依赖的页面,但是这么做之后,服务器就不会返回 404 错误,因为这个时候,所有的页面都会返回 index 页面,为了避免这种情况,在 Vue 应用里面覆盖了所有的路由之后,然后再给出一个 404 页面。