Vue预习课:路由

Vue Router

Vue RouterVue.js 官方的路由管理器。(为了单页面应用程序在不同的视图之间切换,所以需要路由这个库 )
可以查看官方文档看这个库的介绍

安装

vue-cli的环境下,router的安装,其他环境的安装参考文档

  1. vue add router

基础

起步

image.png
路由规划、配置,router/index.js

商品列表(home) - 商品管理(about)

路由出口、导航,App.vue

  1. <nav>
  2. <!-- 导航链接, to是链接到哪个地址 -->
  3. <router-link to="/">首页</router-link>
  4. <router-link to="/about">管理</router-link>
  5. </nav>
  6. <!-- 路由出口 -->
  7. <router-view></router-view>

商品管理,About.vue

  1. <template>
  2. <div>
  3. <message ref="msgSuccess" class="success">
  4. <!-- 命名为title插槽内容 -->
  5. <template v-slot:title="slotProps">
  6. <strong>{{slotProps.title}}</strong>
  7. </template>
  8. <!-- 默认插槽内容 -->
  9. <template v-slot:default>新增课程成功!</template>
  10. </message>
  11. <message ref="msgWarning" class="warning">
  12. <!-- 命名为title插槽内容 -->
  13. <template v-slot:title>
  14. <strong>警告</strong>
  15. </template>
  16. <!-- 默认插槽内容 -->
  17. <template v-slot:default>请输入课程名称!</template>
  18. </message>
  19. <cart-add v-model="course" @add-course="addCourse"></cart-add>
  20. <course-list :courses="courses"></course-list>
  21. </div>
  22. </template>
  23. <script>
  24. import CartAdd from "@/components/CartAdd.vue";
  25. import CourseList from "@/components/CourseList.vue";
  26. import Message from "@/components/Message.vue";
  27. import { getCourses } from "@/api/course";
  28. export default {
  29. name: "app",
  30. data () {
  31. return {
  32. course: "",
  33. courses: []
  34. };
  35. },
  36. components: {
  37. CartAdd,
  38. CourseList,
  39. Message
  40. },
  41. async created () {
  42. // 组件实例已创建,由于未挂载,dom不存在
  43. const courses = await getCourses();
  44. this.courses = courses;
  45. },
  46. methods: {
  47. addCourse () {
  48. if (this.course) {
  49. // 添加course到数组
  50. this.courses.push({ name: this.course, price: 8999 });
  51. this.course = "";
  52. // 显示提示信息
  53. // this.show = true
  54. this.$refs.msgSuccess.toggle();
  55. } else {
  56. // 显示错误信息
  57. // this.showWarn = true
  58. this.$refs.msgWarning.toggle();
  59. }
  60. }
  61. }
  62. };
  63. </script>

动态路由匹配

我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在 vue-router 的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果:

  1. { path: '/user/:id', component: User }
  2. // 也可以是多个不同参数/user/:id/:name

范例:查看课程详情,views/Detail.vue

  1. <div>
  2. <h2>detail page</h2>
  3. <!-- 获取动态路由匹配中通过路由传递的参数 -->
  4. <p>{{$route.params.name}} ...</p>
  5. </div>

router/index.js

  1. {
  2. path: '/course/:name',
  3. // 懒加载组件,增加初始化页面的速度,减少初始化页面大小的体积,增强用户的体验
  4. // 异步加载组件
  5. component: () => import('../views/Detail.vue')
  6. }

列表中的导航,About.vue

  1. // 使用了反单引号
  2. <router-link :to="`/course/${c.name}`">
  3. {{ c.name }} - {{ c.price | currency('¥') }}
  4. </router-link>

通配符

适合做 404 页面路由

路由的匹配是从上往下找的,如果路由中写了两个相同路径的路由配置,则以最上面的为准.当路由配置中都匹配结束之后都没有找到合适的匹配项,则使用通配符匹配的路径

  1. {
  2. // 会匹配所有路径
  3. path: '*',
  4. component: () => import('../views/404.vue')
  5. }

嵌套路由

实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件,例如:
image.png

范例:嵌套方式显示课程详情

  1. <router-link :to="`/about/${c.name}`">
  2. {{ c.name }} - {{ c.price | currency('¥') }}
  3. </router-link>
  4. <!-- 如果路由配置中配置了组件,则在下面router-view的区域展示,否则不展示 -->
  5. <!-- 嵌套路由出口 -->
  6. <router-view></router-view>

路由配置

  1. {
  2. // path可以写相对路径,也可以写绝对路径,建议绝对路径
  3. path: '/about',
  4. name: 'about',
  5. component: () => import(/* webpackChunkName: "about" */
  6. '../views/About.vue'),
  7. children: [
  8. {
  9. path: ':name',
  10. component: () => import('../views/Detail.vue')
  11. },
  12. ]
  13. }

响应路由参数变化,Detail.vue

// 因为在组件创建的时候,需要获取相关信息。但是因为为了提升代码执行效率,在组件复用的情况下,该组件不会被销毁和重建,所以这里需要监听路由的变化。 immediate表示组件初始化时也执行handler

  1. export default {
  2. watch: {
  3. $route: {
  4. handler: () => {
  5. console.log("$route change");
  6. },
  7. // immediate表示组件初始化时也执行handler
  8. immediate: true
  9. }
  10. }
  11. };

编程导航

借助 router 的实例方法,可编写代码来实现编程式导航

路由跳转
location: 地址(想要访问的地址)
onComplete:路由完成之后回调函数
onAbort: 用户取消之后回调函数
router.push(location, onComplete?, onAbort?) (底层使用了historyAPI的方法)

  1. // 字符串
  2. router.push('home')
  3. // 对象
  4. router.push({ path: 'home' })
  5. // 命名的路由
  6. router.push({ name: 'user', params: { userId: '123' }})
  7. // 带查询参数,变成 /register?plan=private
  8. router.push({ path: 'register', query: { plan: 'private' }})

范例:修改为课程详情跳转为编程导航

  1. <!-- @click中可以写多个语句,建议不要这么使用 -->
  2. <div @click="selectedCourse = c;$router.push(`/about/${c.name}`)">
  3. {{ c.name }} - {{ c.price | currency('¥') }}</div>

在实际工作中使用路由的名称的频率较高,因为路径太长

命名路由

建议使用下面的方式
通过一个名称来标识一个路由显得更方便一些,特别是在链接一个路由,或者是执行一些跳转的时候。你可以在创建 Router 实例的时候,在 routes 配置中给某个路由设置名称。

  1. const router = new VueRouter({
  2. routes: [
  3. {
  4. path: '/user/:userId',
  5. name: 'user',
  6. component: User
  7. }
  8. ]
  9. })

要链接到一个命名路由,可以给 router-linkto 属性传一个对象:

  1. <router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>

调用 router.push() 时:

  1. router.push({ name: 'user', params: { userId: 123 }})

如果遇到切换路由组件不重新渲染的问题,解决方案如下:

  1. // 因为在组件创建的时候,需要获取相关信息。但是因为为了提升代码执行效率,在组件复用的情况下,该组件不会被销毁和重建,所以这里需要监听路由的变化
  2. watch: {
  3. // 监听路由
  4. $route: {
  5. handler () {
  6. console.log(`根据传输的${this.$route.params.id}获取值`)
  7. },
  8. immediately: true // 初始化的时候也执行
  9. }
  10. }

进阶

路由守卫

vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。(为了让没有权限访问某个路径的用户,无法访问哪个路径,也就是路由的权限问题)

全局守卫

  1. // (to(去哪里), from(来自哪里), next(是否放行))
  2. router.beforeEach((to, from, next) => {
  3. // ...
  4. // to: Route: 即将要进入的目标 路由对象
  5. // from: Route: 当前导航正要离开的路由
  6. // next: Function: 一定要调用该方法来 resolve 这个钩子。
  7. })

范例:守卫About.vue

  1. router.beforeEach((to, from, next) => {
  2. // 判断路由看是否需要守卫
  3. // 是否登录
  4. if (to.meta.auth) {
  5. if (window.isLogin) {
  6. // 放行
  7. next()
  8. } else {
  9. // to.fullPath为了让用户登录之后可以跳转到原来它想去的页面
  10. next('/login?redirect=' + to.fullPath)
  11. }
  12. } else {
  13. // 放行
  14. next()
  15. }
  16. })
  1. {
  2. path: '/about',
  3. meta: {
  4. auth: true
  5. }
  6. },
  7. {
  8. path: '/login',
  9. // 异步
  10. component: () => import('../views/Login.vue')
  11. },
  1. <template>
  2. <div>
  3. <button @click="login" v-if="!isLogin">登录</button>
  4. <button @click="logout" v-else>登出</button>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. methods: {
  10. login () {
  11. window.isLogin = true
  12. this.$router.push(this.$route.query.redirect)
  13. },
  14. logout () {
  15. window.isLogin = false
  16. this.$router.push('/')
  17. }
  18. },
  19. computed: {
  20. isLogin () {
  21. return window.isLogin
  22. }
  23. },
  24. }
  25. </script>

路由独享的守卫

可以路由配置上直接定义 beforeEnter 守卫:

  1. {
  2. path: '/about',
  3. name: 'about',
  4. // ...
  5. beforeEnter (to, from, next) {
  6. if (window.isLogin) {
  7. next()
  8. } else {
  9. next('/login?redirect=' + to.fullPath)
  10. }
  11. }
  12. },

组件内守卫

可以在路由组件内直接定义以下路由导航守卫:

  • beforeRouteEnter
  • beforeRouteUpdate
  • beforeRouteLeave
    1. // About.vue
    2. beforeRouteEnter(to, from, next) {
    3. if (window.isLogin) {
    4. next();
    5. } else {
    6. next("/login?redirect=" + to.fullPath);
    7. }
    8. }

数据获取

每次切换路由,组件默认情况下总是会重新创建,数据总是会重新加载 1.每次切换路由获取数据是否必要 2.获取数据的时间点是否合适

路由激活时,获取数据的时机有两个:

  • 路由导航前(组件没有渲染) ```javascript // 组件未渲染,通过给next传递回调访问组件实例 beforeRouteEnter(to, from, next) { getPost(to.params.id, post => { // 当前组件渲染完成之后,vm是当前组件实例 next(vm => vm.setData(post)) }) },

// 组件已渲染,可以访问this直接赋值 beforeRouteUpdate(to, from, next) { this.post = null getPost(to.params.id, post => { this.setData(post) next() }) },

  1. - 路由导航后
  2. ```javascript
  3. created() {
  4. this.fetchData()
  5. },
  6. watch: {
  7. // 监听路由变化
  8. '$route': 'fetchData'
  9. }

动态路由 —- 动态配置路由

动态添加路由。接收的参数是数组 动态配置路由

通过router.addRoutes(routes)方式动态添加路由

  1. // 全局守卫修改为:要求用户必须登录,否则只能去登录页
  2. router.beforeEach((to, from, next) => {
  3. // 判断逻辑:
  4. // 是否登录
  5. if (window.isLogin) {
  6. if (to.path === '/login') {
  7. next('/')
  8. } else {
  9. next()
  10. }
  11. } else {
  12. // 没有登录
  13. if (to.path === '/login') {
  14. next()
  15. } else {
  16. next('/login?redirect=' + to.fullPath)
  17. }
  18. }
  19. })

在登录页面登陆成功之后,动态添加路由

  1. // Login.vue用户登录成功后动态添加/about
  2. login() {
  3. window.isLogin = true;
  4. // 动态添加路由
  5. this.$router.addRoutes([
  6. {
  7. path: "/about", //...
  8. }
  9. ]);
  10. const redirect = this.$route.query.redirect || "/";
  11. this.$router.push(redirect);
  12. }

路由组件缓存

路由切换,组件频繁加载,数据频繁加载,浪费资源,可以使用组件缓存.让组件不重置状态,不销毁组件

利用keepalive做组件缓存,保留组件状态,提高执行效率
可以使用include来配置存活的组件,组件直接使用逗号隔开。exclude来配置不需要存活的组件
缓存东西越多,占用资源越大。可以使用max,max传入的是缓存组件的个数,如果配置的是10个,则超出10个的就进入一个新的缓存组件,出去一个最老的缓存组件,保证资源的合理利用

keep-alive 这个组件对组件中的name有依赖 include:配置能存活的组件(值是:用逗号隔开的字符串,每个字符串表示在创建组件时,在组件内部写的name,不是在路由中配置的name。值如果是动态的,则可以是数组,如下:)

exclude:排除某几个 max:最大缓存的数量。max传入的是缓存组件的个数,如果配置的是10个,则超出10个的就进入一个新的缓存组件,出去一个最老的缓存组件,保证资源的合理利用

范例:缓存about组件

  1. <keep-alive include="about">
  2. <router-view></router-view>
  3. </keep-alive>
  4. 也可以使用下面的写法
  5. <keep-alive :include="['about']">
  6. <router-view></router-view>
  7. </keep-alive>

使用include或exclude时要给组件设置name,不是配置路由的时候的那个name

两个特别的钩子:activated、deactivated

在keep-alive组件中存在activated、deactivated这两个钩子
activated:组件激活状态
deactivated:组件失活状态

路由懒加载

路由组件的懒加载能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

  1. () => import("../views/About.vue")

把组件按组分块

  1. () => import(/* webpackChunkName: "group-about" */ "../views/About.vue")