参考视频教程资料:    Vue.js源码全方位深入解析 (含Vue3.0源码分析)  :  http://www.notescloud.top/goods/detail/1188   微信分享与支付专项课程 (公众号 小程序(云))  :  http://www.notescloud.top/goods/detail/1198   深入Vue3+TypeScript技术栈-coderwhy大神新课  :  http://www.notescloud.top/goods/detail/1197   Vue3.0高阶实战:开发高质量音乐Web app  :  http://www.notescloud.top/goods/detail/1190 <br />

文章目录

<br />

官方文档:
https://router.vuejs.org/zh/installation.html

一、路由规则(基础)

所谓前端路由,即改变URL,但是页面不进行整体的刷新。实现方式主要有下面两种方式

1. URL的hash

URL 的 hash 也就是锚点 (#), 本质上是改变 window.locationhref 属性。可以通过直接赋值 location.hash 来改变 href,但是页面不发生刷新

直接在浏览器的命令行测试:


> location.href
"http://localhost:8080"
> location.hash='/foo'
"/foo"
> location.href
"http://localhost:8080/#/foo"

查看 Network,可以看到页面并没有刷新

2. HTML5的history模式

history 接口是 HTML5 新增的, 它有五种模式改变 URL 而不刷新页面

  1. history.pushState(data: any, title: string, url?: string | null)
    可以理解为压栈,将新的 url 压入栈中,浏览器的 地址栏只显示栈中最上面的 url

   > location.href
   "http://localhost:8080/"
   > history.pushState({},'',"/app")
   undefined
   > location.href
   "http://localhost:8080/app"
   
  1. history.replaceState(data: any, title: string, url?: string | null)
    不保留当前 url,直接替换。

  2. history.go(delta?: number)
    传入一个数字,正数表示向前跳转几次,负数表示向后跳转几次,


   > location.href
   "http://localhost:8080/"
   > history.pushState({},'',"/app")
   undefined
   > location.href
   "http://localhost:8080/app"
   > history.replaceState({},'','/demo')
   undefined
   > location.href
   "http://localhost:8080/demo"
   > history.go(-1)
   undefined
   > location.href
   "http://localhost:8080/"
   
  1. history.back()
    等价于:history.go(-1)

  2. history.forward()
    等价于:history.go(1)

二、安装与使用vue-router

1. 安装

在使用脚手架创建项目的时候直接添加 Router,也可以使用命令在项目中添加:


npm install vue-router --save

2. vue-router 使用步骤:

大体步骤:

  1. 导入路由对象,并且调用 Vue.use(VueRouter)
  2. 创建路由实例,并且传入路由映射配置
  3. 在Vue实例中挂载创建的路由实例

例子:

  • src/router/index.js:一般的,router 相关的配置信息,都放在 src/router/index.js

  import Vue from "vue"
  // 1. 导入 VueRouter 插件
  import VueRouter from "vue-router";

  // 2.通过 Vue.use(VueRouter),安装插件
  Vue.use(VueRouter);

  // 3. 创建 VueRouter 对象并配置映射关系
  const router = new VueRouter({
    // 配置路由和组件之间的应用关系
    routes: [

    ]
  })

  // 4. 将 router 对象传入到 Vue 实例中
  export default router;
  
  • main.js

  import Vue from 'vue'
  import App from './App.vue'
  // 5. 导入,如果导入的是一个文件夹,会自动找到文件夹中 index.js 文件
  import router from "./router";

  Vue.config.productionTip = false

  new Vue({
    // 6. 挂载创建的路由实例
    router,
    render: h => h(App),
  }).$mount('#app')
  

3. 路由与组件映射


const router = new VueRouter({
  // 配置路由和组件之间的应用关系
  routes: [
    {
      // 路由的默认路径(指定刚进入页面时,选择哪个路由)
      path: '/',
      redirect: '/home'
    },
    {
      path: '/home',
      component: () => import("../components/Home")
    },
    {
      path: '/about',
      component: () => import("../components/About")
    }
  ]
})

4. 使用路由( router-link 和 router-view 标签)

<router-link><router-view> 标签都是 vue-router 中已经内置的组件。

  • <router-link>:用来改变地址栏中的地址

    • to:用来指定路径,默认使用的是锚点。

    • tag:tag可以指定 <router-link> 之后渲染成什么组件,默认会被渲染成一个 <a> 标签

    • replace:使用该参数后不会留下 history 记录,所以指定 replace 的情况下,后退键返回不能返回到上一个页面中

      (可想而知,默认底层使用 history.pushState() 改变 url,加上 replace 后,就使用的是 history.replaceState() 来改变url )

    • active-class:当 <router-link> 对应的路由匹配成功时, 会自动给当前元素设置一个 router-link-active 的 class,可以通过设置 active-class 来修改这个默认的 class 名称。(全部修改请查看第三章第2节)

  • <router-view>:该标签会根据当前的路径,动态渲染出不同的组件.

    网页的其他内容,比如顶部的标题/导航,或者底部的一些版权信息等会和<router-view>处于同一个等级。在路由切换时,切换的是 <router-view> 挂载的组件,其他内容不会发生改变.

    • key:标识当前组件,默认使用的是路由,当路由中存在参数时,可以使用全路径。(详见 八-1)

<template>
  <div id="app">
    <router-link to="/home">首页</router-link>
    <router-link to="/about" tag="li" replace>关于</router-link>

    <router-view></router-view>
	<!-- 使用全路径作为标识 -->
	<router-view :key="$route.fullPath"></router-view>
  </div>
</template>

5. 事件中使用路由($router)

可以不使用 <router-link> 而是直接使用按钮等其它标签,然后绑定事件,通过事件来改变 url:


<!-- push 与 replace 的区别就是会不会留下 history 记录 -->
<button @click="$router.push('/home')">Home</button>
<button @click="$router.replace('/about')">About</button>

vue 中通过 this 来访问 $router 属性


export default {
  name: 'App',
  method:{
    clickFunc() {
      this.$router.replace("/home");
    }
  }
}

  • 注意:不要使用 history.pushState()history.replaceState() 等方法来直接修改 url,这样会绕过 vue-router

三、创建 VueRouter 对象时的其它配置

  1. mode(指定路由模式)

默认情况下,路径的改变使用的 URL 的 hash。使用如下方式,在创建 VueRouter 对象时,更改 mode 属性,设置路由模式为 HTML5 的 History 模式


const router = new VueRouter({
  // 配置路由和组件之间的应用关系
  routes: [
  ],
  // 指定路由模式
  mode: 'history'
})

  1. linkActiveClass(路由匹配成功时添加 class)

<router-link> 对应的路由匹配成功时, 会自动给当前元素设置一个 router-link-active 的 class。

一般在进行高亮显示的导航菜单或者底部 tabbar 时, 会使用到该类。但是通常不会修改类的属性, 会直接使用默认的 router-link-active 即可

也可以使用 linkActiveClass 来修改这个默认的 class。


const router = new VueRouter({
  // 配置路由和组件之间的应用关系
  routes: [
  ],
  // 指定路由模式
  linkActiveClass: "active"
})

四、传递参数($route)

  1. 动态路由(params的类型)

要给 User 组件传入一个 ID,那么,我们可以在 vue-router 的路由路径中使用"动态路径参数 "(dynamic segment) 来达到这个效果
一个"路径参数 "使用冒号 : 标记。当匹配到一个路由时,参数值会被设置到 this.$route.params $route

  • $route:当前哪个路由处于活跃状态,拿到的就是哪个路由。即 routes 数组中对应的路由对象

    注意与 $router 的区别,$router 是指 new 出来的 VueRouter 对象,也就是下面的的常量 router,而 $routeVueRouter 对象中的 routers 数组中的一个对象,即 $router.routes[0] 的路由是处于活跃状态时就是 $route

  1. 在 vue-router 映射关系的 path 中,增加一个 idProp 参数,来接收上面传入的 id,这个参数传递进来后会保存在 $route.idProp

const router = new VueRouter({
  // 配置路由和组件之间的应用关系
  routes: [
    {
      path: '/user/:idProp',
      component: () => import("../components/User")
    }
  ],
  mode: 'history'
})

  1. url 中加入的用户的 id 参数:

<!-- 使用 router-link 的方式 -->
<router-link :to="'/user/' + id" tag="button">用户</router-link>

<!-- 使用 JavsScript 的方式 -->
<button @click="$router.push('/user/' + id)">用户</button>

  1. 使用 $route.params 获取到传入的 idProp 参数

<template>
  <div>
    <h3>这是直接获取的用户id:{{$route.params.idProp}}</h3>
    <li>这是从方法中获取的id:{{id2}}</li>
  </div>
</template>

<script>
  export default {
    name: "User",
    computed: {
      id2() {
        return this.$route.params.idProp;
      }
    }
  }
</script>

  1. query的类型

配置路由格式:/router,也就是普通配置
传递的方式:直接传递对象,对象中用 path 来指定路由,用 query 对象来传递参数
传递后形成的路径:/router?id=123/router?id=abc

  1. 传递参数:直接传递一个对象,对象的 path 用来指定路由,query 用来指定参数

   <!-- 使用 router-link 的方式 -->
   <!-- 请求地址:http://localhost:8080/profile?name=%E6%9D%8E%E5%9B%9B&age=24&heigth=1.88 -->
   <router-link :to="{path: '/profile', query: {name: '张三', age: 34}}">张三档案</router-link>

   <!-- 使用 JavsScript 的方式 -->
   <button @click="$router.push({path: '/profile', query: {name: '李四', age : 24, heigth: 1.88}})">李四档案</button>
   
  1. 接收参数:Profile.vue 中使用 $route.query 来获取传入的 query 对象

   <template>
     <div>
       <li v-for="(val, key) in $route.query">{{key}}: {{val}}</li>
     </div>
   </template>
   

扩展

  • URL的完整路径各个部分的叫法,这也解释了上面为什么使用 query 来字义参数:

> scheme:[//[user:password@]host[:port]][/]path[?query][#fragment] 
> 

五、嵌套路由

嵌套路由是一个很常见的功能,比如在 home 页面中,我们希望通过 /home/news 或者 /home/message 访问一些内容。一个路径映射一个组件,访问这两个路径也会分别渲染两个组件。比如说,当访问 /home/news 时,先渲染 Home 组件,再在 Home 组件中渲染 News 组件。

路径和组件的关系如下:

在这里插入图片描述

实现嵌套路由主要是两个步骤:

  1. 创建对应的子组件,并且在路由映射的 children 中配置对应的子路由.

export default new VueRouter({
  // 配置路由和组件之间的应用关系
  routes: [
    {
      path: '/home',
      component: () => import("../components/Home"),
      children:[
        {
          path: '',
          redirect: 'news'
        },
        {
          // 注意路径前的 /,加上表示绝对路径,不加表示相对路径
          // 下面的配置匹配的路径为:/home/news
          path: 'news',
          component: () => import("../components/HomeNews")
        },
        {
          path: 'messages',
          component: () => import("../components/HomeMessages")
        }
      ]
    }
  ]
})

  1. 在组件内部使用 <router-view> 标签.

<template>
  <div>
    <h2>我是Home</h2>
    <p>我是Home的内容</p>

    <button @click="$router.replace('/home/news')">首页新闻</button>
    <button @click="$router.replace('/home/messages')">首页消息</button>
    <router-view></router-view>
  </div>
</template>

六、导航守卫

vue-router 提供的导航守卫主要用来监听路由的进入和离开 的。导航守卫这块官网也说的比较清楚了,这块直接看官网就好(官方网址:戳这里

这里就给全局守卫举个例子,其它的用法也基本相同。

全局前置守卫

vue-router 提供了 beforeEachafterEach 的全局钩子函数,它们会在路由即将改变前和改变后触发。

使用 router.beforeEach 注册一个全局前置守卫:


const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
  next();
})

当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。

每个守卫方法接收三个参数:

  • to: Route: 即将要进入的目标 路由对象

  • from: Route: 当前导航正要离开的路由

  • next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。

    • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。

    • next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。

    • next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。

    • next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

  • 举个小例子:


  const router = new VueRouter({
    routes: [
      {
        path: '/home',
        component: () => import("../components/Home"),
        // 使用 meta 属性来添加数据
        meta: {
          title: '首页'
        }
      },
      {
        path: '/about',
        component: () => import("../components/About"),
        meta: {
          title: '关于'
        },
      }
    ],
    mode: 'history'
  })

  router.beforeEach((to, from, next) => {
    // 路由改变时,修改标题
    document.title = to.meta.title;

    // 如果是子组件的话,要这么取 meta 中的值:(to.matched是个数组,包含所有子组件)
    document.title = to.matched[0].meta.title;
    next();
  });
  

七、路由懒加载

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。

路由懒加载的主要作用就是将路由对应的组件打包成一个个的 js 代码块。只有在这个路由被访问到的时候,才加载对应的组件。

结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载(之前的案例都是这种写法,这里就是再说明一下):


const router = new VueRouter({
  // 配置路由和组件之间的应用关系
  routes: [
    {
      path: '/home',
      // 下面的写法就是路由懒加载写法
      component: () => import("../components/Home")
    },
    {
      path: '/about',
      component: () => import("../components/About")
    }
  ]
})

八、遇到的问题

  1. 多路由共用同一组件,相互干扰问题

  • 问题描述:一个组件,要根据传入的参数不同,显示不同的数据,同时这个组件的状态还要保留(<keep-alive>)。这个时候就出现了一个问题,当一个组件的数据改变时,其它组件的数据也会跟着改。

  • 解决办法:


  <router-view :key="$route.fullPath"/>