vue 系列 -- vue-router 两种路由模式

902 阅读5分钟

前言

在一个网页里面,多个子网页之间需要相互切换

类似于这样:

20210704_194757.gif

之前的解决方式是前后端配合,一个子网页对应服务器上的一个子文件夹,用户每点击一个按钮就向后端请求数据:

image.png

通过 v-show 去控制组件的显示与隐藏

代码逻辑如下:

<div v-for="(item,index) in list" v-show = {{activeIndex === index}}>{{item.title}}</div>

image.png

缺点

可以看到:由于 SPA 中用户的交互是 通过 JS 改变 HTML 内容 来实现的,也就是说 页面本身的 url 并没有变化,这导致了三个问题:

  • SPA 无法记住用户的操作记录,无论是刷新、前进还是后退,都无法展示用户真实的期望内容。
  • SPA 首次加载时间长,需要把所有的内容加载出来(包括看不见的)
  • SPA 中虽然由于业务的不同会有多种页面展示形式,但只有一个 url,对 SEO 不友好,不方便搜索引擎进行收录。

这里不用 v-if 而是用 v-show 的原因是:Tab 页面会不断切换,如果使用 v-if 就会造成 DOM 节点的摘除和接上,可能会引起回流

为了解决上述三个问题,就有了 前端路由 的概念

定义

什么是路由?

路由这个概念最先是后端出现的,简单来说 路由就是用来跟后端服务器进行交互的一种方式,通过不同的路径,来请求不同的资源,请求不同的页面是路由的其中一种功能

什么是前端路由?

简单的说,就是在保证只有一个 HTML 页面,且与用户交互时不刷新和跳转页面的同时,为 SPA 中的每个视图展示形式匹配一个特殊的 url。在刷新、前进、后退和SEO时均通过这个特殊的 url 来实现。

为实现这一目标,我们需要做到以下两点:

  • 改变 url 且不让浏览器像服务器发送请求
  • 可以监听、记录到 url 的变化

vue-router 中就有两种路由模式:hash 模式、history 模式

hash 模式

原理

  • 早期的前端路由的实现就是基于 location.hash 来实现的,location.hash 的值就是 URL 中 # 及后面的内容。它的实现原理就是监听 # 后面的内容来发起 Ajax 请求来进行局部更新,而不需要刷新整个页面。
  • 使用 hashchange 事件来监听 URL 的变化,以下这几种情况改变 URL 都会触发 hashchange 事件:
    • 浏览器前进后退改变 URL
    • a 标签改变 URL
    • window.location 改变 URL

image.png

image.png

使用

<ul id="menu">
  <li>
    <a href="#index">首页</a>
  </li>
  <li>
    <a href="#news">资讯</a>
  </li>
  <li>
    <a href="#user">个人中心</a>
  </li>
</ul>
<div id="app"></div>
function hashChange(e){
    let app = document.getElementById('app')
    switch (location.hash) {
      case '#index':
        app.innerHTML = '<h1>这是首页内容</h1>'
        break
      case '#news':
        app.innerHTML = '<h1>这是新闻内容</h1>'
        break
      case '#user':
        app.innerHTML = '<h1>这是个人中心内容</h1>'
        break
      default:
        app.innerHTML = '<h1>404</h1>'
    }
}
window.onhashchange = hashChange
hashChange() // 初始状态渲染

效果

20210705_101557.gif

优点

  • 兼容低版本浏览器
  • 只有 # 符号之前的内容才会包含在请求中被发送到后端,也就是说就算后端没有对路由全覆盖,但是不会返回 404 错误
  • hash 值的改变,都会在浏览器的访问历史中增加一个记录,所以可以通过浏览器的回退、前进按钮控制 hash 的切换会覆盖锚点定位元素的功能

缺点

不太美观,# 后面传输的数据复杂的话会出现问题

history 模式

原理

  • history 提供了 pushState 和 replaceState 两个方法来记录路由状态,这两个方法改变 URL 不会引起页面刷新
  • history 提供类似 hashchange 事件的 popstate 事件,但 popstate 事件有些不同:通过浏览器前进后退改变 URL 时会触发 popstate 事件,通过 pushState/replaceState 或 a 标签改变 URL 不会触发 popstate 事件。好在我们可以拦截 pushState/replaceState 的调用和 a 标签的点击事件来检测 URL 变化,所以监听 URL 变化可以实现,只是没有 hashchange 那么方便。
  • pushState(state, title, url) 和 replaceState(state, title, url)都可以接受三个相同的参数

使用

<ul id="menu">
  <li>
    <a href="/index">首页</a>
  </li>
  <li>
    <a href="/news">资讯</a>
  </li>
  <li>
    <a href="/user">个人中心</a>
  </li>
</ul>
<div id="app"></div>
document.querySelector('#menu').addEventListener('click',function (e) {
  if(e.target.nodeName ==='A'){
    e.preventDefault()
    let path = e.target.getAttribute('href')  //获取超链接的href,改为pushState跳转,不刷新页面
    window.history.pushState({},'',path)  //修改浏览器中显示的url地址
    render(path)  //根据path,更改页面内容
  }
})

function render(path) {
  let app = document.getElementById('app')
  switch (path) {
    case '/index':
      app.innerHTML = '<h1>这是首页内容</h1>'
      break
    case '/news':
      app.innerHTML = '<h1>这是新闻内容</h1>'
      break
    case '/user':
      app.innerHTML = '<h1>这是个人中心内容</h1>'
      break
    default:
      app.innerHTML = '<h1>404</h1>'
  }
}
window.onpopstate = function (e) {
  render(location.pathname)
}
render('/index')

优点

  • 使用简单,比较美观
  • pushState() 设置新的 URL 可以是任意与当前 URL 同源的 URL,而hash只能改变#后面的内容,因此只能设置与当前URL同文档的URL
  • pushState() 设置的 URL 与当前 URL 一模一样时也会被添加到历史记录栈中,而 hash #后面的内容必须被修改才会被添加到新的记录栈中
  • pushState() 可以通过 stateObject 参数添加任意类型的数据到记录中,而hash只能添加短字符串
  • pushState() 可额外设置 title 属性供后续使用

缺点

  • 前端的 URL 必须和向发送请求后端 URL 保持一致,否则会报 404 错误
  • 由于 History API 的缘故,低版本浏览器有兼容问题

两种模式的不同使用场景

  • 从上文可见,hash 模式下 url 会带有#,当你希望 url 更优雅时,可以使用 history 模式。
  • 当使用 history 模式时,需要注意在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。
  • 当需要兼容低版本的浏览器时,建议使用 hash 模式。
  • 当需要添加任意类型数据到记录时,可以使用 history 模式

参考文章