理解 vue-router

447 阅读6分钟

前端开发工作一年多,搬砖之余,整理下对 vue-router 的理解。理解有偏差之处希望大家指出 :smile:

什么是 vue-router

vue-router 是 vue 官方的路由管理器,它和 Vue.js 的核心深度集成,由构建单页面应用(spa)变得易如反掌。 vue-router 通过 vue 的插件系统注入到 vue 中。

  • 嵌套的路由/视图表
  • 模块化的、基于组件的路由配置
  • 路由参数、查询、通配符
  • 基于 Vue.js 过渡系统的视图过渡效果
  • 细粒度的导航控制
  • 带有自动激活的 CSS class 的链接
  • HTML5 历史模式或 hash 模式,在 IE9 中自动降级
  • 自定义的滚动条行为

使用详情看官方文档,这不是文章重点

vue-router 的由来不得不再介绍一下 spa

什么是 spa

SPA 是一种 网络应用程序(WebApp)模型。在传统的网站中,不同的页面之间的切换都是直接从服务器加载一整个新的页面,而在 SPA 这个模型中,是通过动态地重写页面的部分与用户交互,而避免了过多的数据交换,响应速度自然相对更高,就是现在提倡的前后端分离。 简而言之 spa 加载的时候会需要下载 SPA 框架 (React, Vue, Angular)等,请求整个项目的资源。

spa 优点

  • 页面路由之间的切换非常快 :laughing:
  • 一定程度上减少了后端服务器的鸭梨
  • 后端只需提供 api, 不用考虑是什么端的应用 web 还是移动端等,减少了后端的压力。前后端分离的核心,各司其职

spa 的缺点

  • 首屏速度慢,白屏时间长 (毕竟一次加载了所有资源)
  • 对 seo 不友好

vue-router 在 spa 中起的作用

spa 应用既然会将整个资源请求过来,那么就需要前端做一个路由管理,前端的路由其实是假路由,起的作用是资源与 route 之间的映射作用。前端自己设定路由规则名称。vue-router + vue异步组件+ webpack 实现资源的懒加载来提升性能等。

vue-router 的模式

vue-router 的模式氛围 hash 模式 和 history 模式,共同之处都是 url 改变不会向后端发起请求

hash 模式

地址栏中的 # 符号,如这个 www.abc.com/#/ui, hash 值是 #/ui, 特点:hash 虽然出现再 url 中,但是不会被包括在 HTTP 请求中,对后端没有影响,因此 hash 改变不会重新加载页面。

history 模式

利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。(需要特定浏览器支持) 这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。

使用注意细节

  • hash 模式会多一个 #,一般场景下都可以完成需求,比较在意颜值的可以考虑用 history 模式。

  • 关于 history 模式经常会刷出 404 的问题: 前端向后端发起请求的时候,hash 模式下, 仅 hash 符号之前的内容会被包含在请求中。 如:

    http://www.abc.com/#/ui 仅 http://www.abc.com/ 会被包含在请求中

    history 模式下
    http://www.abc.com/ui url中 http://www.abc.com/ui 会被包含在请求中

    切换两个路由模式验证一下,会发现 hash 模式下的
    location.pathname 为: "/"。 而 history 模式下的 location.pathname 为 "/ui"。

    所以 history 模式下如果后端路由没有做到全覆盖,就容易返回 404 错误。所以要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面。即 dist 文件夹下的 index.html

    vue-router 模式详情参考 juejin.cn/post/684490…

webpack 需要对 vue-router 做什么

开发模式下的 404 情况

在开发模式下, 如果路由 mode 为 history,那么需要在 webpack 中进行如下配置来防止刷出 404 的情况,说明是 webpack 打包时,index.html 指定出现了问题,可以在 webpack 配置文件中设置 historyApiFallback 来指定模板路径。

  devServer: {    
    contentBase: path.resolve(__dirname + '../dist'),    
    host: '0.0.0.0',    
    port: '8080',   
    hot: true,    
    inline: true,    
    historyApiFallback: {        
        index: '/index.html'//index.html为当前目录创建的template.html    
    }    
   // open: true,
  }

但是在生产环境下,还是需要后端做一下匹配不到静态资源时 返回 index.html 的情况。

路由懒加载

结合 Vue 的 异步组件 和 Webpack 的 code splitting feature, 轻松实现路由组件的懒加载。(当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了 )。

  1. babel 支持
  • npm install babel-plugin-dynamic-import-webpack --save

  • .babelrc 文件中添加一个 plugin

    "plugins": ["babel-plugin-dynamic-import-webpack"]

  1. 定义一个能够被 webpack 自动代码分割的异步组件

    const Foo = () => import('./Foo.vue')

  2. 在路由配置中什么都不需要改变,只需要像往常一样使用 Foo:

  const router = new VueRouter({
  routes: [
    { path: '/foo', component: Foo }
  ]
  })

官网 中这样描述:

可以将异步组件定义为返回一个 Promise 的工厂函数(该函数返回的 Promise 应该 resolve 组件本身):

const Foo = () => Promise.resolve({ /* 组件定义对象 */ })

在 webpack 2 中,我们可以使用动态 import 语法来定义代码分块点(split point):

import('./Foo.vue') // returns a Promise

验证异步组件: index.js 文件

  //index.js
  import('./a').then(a => {
      const bar = a.bar;
      bar();
  });

a.js 文件

  //a.js
  function bar () {
      return 1;
  }

  module.exports = {
      bar
  }

看一下这两个文件最后 webpack 打包后的产物 会有两个文件 main.js 和 0.main.js 很容易猜到,这个 0.main.js 也就是按需分割出来的代码,也就是 a.js 的内容。 这里 main.js 就只说与普通的 webpack 模块不一样的地方了。

  new Promise(function(resolve) {
    __webpack_require__.e(0)
        .then((function (require) {
            resolve(__webpack_require__("./src/a.js"));
            }).bind(null,__webpack_require__))
        .catch()
   }).then(function(a) {
    ...
   })

vue 异步组件总结:

  1. 创建 promise,这个很好理解因为这里必须是要变成 promise 给后面调用的。
  2. 创建 script 标签,因为需要的 js 文件,这种 js 的异步加载的方式应该来说是非常多见的了。

总结

理解 vue-router 需要从根本上了解,不仅仅局限于 api 的应用。使用 vue-router 不难,从根本理解了才能更好的解决问题哦,还有理解的有问题的地方欢迎指出哦 :smile: 。另外还有一些问题没有说到,比如在 ssr 中,vue-router 应该怎么处理来防止栈溢出等问题,下次再做一个全面性的关于 vue 的理解吧。