Vue中的路由_路由导航守卫

485 阅读8分钟

1. 路由 - 是什么

序言: 看完 w_y小前端 的这篇文章, 相信对路由_路由导航守卫有一定的理解啦

下面有三种映射关系:

  • 设备和 ip 的映射关系

image-20210512095742920.png


  • 接口和服务的映射关系

image-20210512095805578.png


  • 路径和组件的映射关系

image-20210512095825389.png

  • 什么是路由?
    • 路由是一种映射关系
  • Vue中的路由是什么?
    • 路径和组件的映射关系

2. 路由 - 为何用

线上使用实例: 网易云音乐(老网抑云了...)

  • 单页面应用(SPA): 所有功能在一个html页面上实现
  • 前端路由作用: 实现业务场景切换 优点:
  • 整体不刷新页面,用户体验更好
  • 数据传递容易, 开发效率高

缺点:

  • 开发成本高(需要学习专门知识)
  • 首次加载会比较慢一点。不利于 seo

3. 路由 - Vue-Router 介绍

官网:Vue.js 官方的路由管理器

    vue-router模块包

    它和 Vue.js 深度集成

    可以定义 - 视图表(映射规则)

    模块化的

    提供2个内置全局组件

    声明式导航自动激活的 CSS class 的链接
...

Vue中如何实现路由?

    集成vue-router 模块包
    

4. Vue 路由 - 组件分类

.vue文件分2类, 一个是页面组件, 一个是复用组件

  • 页面组件 : 页面展示 - 配合路由用
  • 复用组件 : 展示数据/常用于复用

image-20210424215930981.png

总结: views下的页面组件, 配合路由切换, components下的一般引入到views下的vue中复用展示数据

5. Vue 路由 - Vue-Router 使用

App.vue

<template>
  <div>
    <div class="footer_wrap">
      <a href="#/find">发现音乐</a>
      <a href="#/my">我的音乐</a>
      <a href="#/part">朋友</a>
    </div>
    <div class="top">
      
    </div>
  </div>
</template>

<script>
export default {};
</script>

<style scoped>
.footer_wrap {
  position: fixed;
  left: 0;
  top: 0;
  display: flex;
  width: 100%;
  text-align: center;
  background-color: #333;
  color: #ccc;
}
.footer_wrap a {
  flex: 1;
  text-decoration: none;
  padding: 20px 0;
  line-height: 20px;
  background-color: #333;
  color: #ccc;
  border: 1px solid black;
}
.footer_wrap a:hover {
  background-color: #555;
}
.top {
  padding-top: 62px;
}
</style>

  • 安装
yarn add vue-router
  • 导入路由
// 1. 引入 VueRouter
import VueRouter from 'vue-router'
  • 使用路由插件
// 2. 使用 Vue.use 将构造函数 VueRouter 加载到 Vue上
// Vue.use() 函数就是在装插件
// 这一步 VueRouter 注册了两个全局组件: RouterLink RouterView
Vue.use(VueRouter)
  • 创建路由规则数组
// 3. 配置路由规则 -- 重点学习的内容, 规则的属性非常重要
let routes = [
  {
    // 重定向: 当用户访问 / 时, 强制跳到 /find
    path: '/', // 用户访问的路径
    redirect: '/find' // 强制跳转的路径
  },
  // 一个对象表示一条映射规则, 请求什么路径 切换什么组件
  {
    // 可以给每一条路由规则定义 name , 将来可以用于编程式导航跳转
    // 建议大家写路由规则时都写上 name
    // name 不需要斜杠开头
    name: 'Find',
    path: '/find',
    // component 是配置规则定的属性名, 咱们不能随便改
    // Cannot read properties of undefined (reading '$createElement')
    component: Find,
    children: [
      // 一个对象表示一条子路由规则
      // 希望用户访问 /find/recommend
      {
        // 子路中不要加 / 开头, 匹配的是一级路由拼接二级路由的效果
        // 如果二级没有写/,需要跟一级拼接,如果二级写了/ 那就跟一级path没有关系
        // /find/recommend
        // path: '/recommend', // 如果不小心写了斜杠开头, 就会直接匹配二级路由的路径并切换组件, 但是没有了高亮效果
        path: 'recommend',
        component: Recommend,
      },
      {
        path: 'ranking',
        component: Ranking
      },
      {
        path: 'songlist',
        component: SongList
      },
    ]
  },
  {
    name: 'My',
    path: '/my',
    component: My
  },
  {
    name: 'Part',
    // 用来匹配 query 传参或无传参的情况
    path: '/part',
    component: Part
  },
  {
    // 定义接收的参数, 动态路径传参
    // 加了冒号 name 就是参数
    // 一定不要忘了加 : , 如果忘了就变成了 /part/name
    // name 最终会被放到 $route.params 中作为 name 属性传递给目标组件
    // 有些论坛会用年月日来作为路径参数查询不同时间的帖子 /2020/08/12/article
    path: '/part/:name/:age',
    component: Part
  },
  // 在配置规则的最底部写通配符 * 匹配所有路径
  // 兜底的规则, 当用户访问的路径都不匹配时, 就跳到这条规则
  {
    path: '*',
    component: NotFound
  }
]

记得引入路由相应的组件

  • 创建路由对象 - 传入规则
// 4. 根据规则创建路由对象
let router = new VueRouter({
  // routes : routes
  routes,
  // 设置路由模式, 取值范围: hash / history
  // hash: 带井号的路径
  // history: 不带#号的路径, 但是需要后端配合
  mode: 'history'
})
  • 关联到vue实例
// 5. 将路由对象注入到 new Vue 创建的实例中
new Vue({
  render: h => h(App),
  router
}).$mount('#app')
  • 使用 router-view 挂载点
<div class="top">
      <router-view></router-view>
</div>

总结:

  1. 下载路由模块, 编写对应规则注入到vue实例上, 使用router-view挂载点显示切换的路由
  2. 一切都围绕着hash值变化为准, 切换url上hash值, 开始匹配规则, 对象组件展示到 router-view位置

6. Vue 路由 - 声明式导航_跳转

可用全局组件router-link来替代 a标签

  1. vue-router提供了一个全局组件 router-link
  2. router-link实质上最终会渲染成a链接 to属性等价于提供 href属性(to无需#)
  3. router-link提供了声明式导航高亮的功能(自带类名)
<template>
  <div>
    <div class="footer_wrap">
      <router-link to="/find">发现音乐</router-link>
      <router-link to="/my">我的音乐</router-link>
      <router-link to="/part">朋友</router-link>
    </div>
    <div class="top">
      <router-view></router-view>
    </div>
  </div>
</template>

<script>
export default {};
</script>

<style scoped>
/* 省略了 其他样式 */
.footer_wrap .router-link-active{
  color: white;
  background: black;
}
</style>

总结 : 链接导航, 用 router-link 配合 to, 实现点击切换路由

7. Vue 路由 - 声明式导航_传参

概念

在跳转路由时, 可以给路由对应的组件内传值

基本用法

在router-link上的to属性传值, 语法格式如下

  • /path?参数名=值&参数名2=值2
  • /path/值 – 需要路由对象提前配置 path: “/path/参数名”
<template>
  <div>
    <div class="footer_wrap">
      <!-- <a href="#/find">发现音乐</a>
      <a href="#/my">我的音乐</a>
      <a href="#/part">朋友</a> -->
      <!-- router-link 写的就是声明式导航 -->
      <router-link to="/find">发现音乐</router-link>
      <router-link to="/my">我的音乐</router-link>
      <router-link to="/part?name=砂糖橘&age=18">朋友 - query传参</router-link>
      <router-link to="/part/砂糖橘/18">朋友 - params传参</router-link>
    </div>
    <div class="top">
      <router-view></router-view>
    </div>
  </div>
</template>

对应页面组件接收传递过来的值

  • $route.query.参数名
  • $route.params.参数名
  1. 组件中准备接收路由上传递的参数和值
<template>
  <div>
    伙伴
    <!-- $route : 路由参数对象, 用于接收参数 -->
    <p>接收到了 query 参数: {{ $route.query.name }} --- {{ $route.query.age}}</p>
    <p>接收到了 params 参数: {{ $route.params.name }} --- {{ $route.params.age }}</p>
  </div>
</template>
  1. 路由定义
{
    name: 'Part',
    // 用来匹配 query 传参或无传参的情况
    path: '/part',
    component: Part
  },
  {
    // 定义接收的参数, 动态路径传参
    // 加了冒号 name 就是参数
    // 一定不要忘了加 : , 如果忘了就变成了 /part/name
    // name 最终会被放到 $route.params 中作为 name 属性传递给目标组件
    // 有些论坛会用年月日来作为路径参数查询不同时间的帖子 /2020/08/12/article
    path: '/part/:name/:age',
    component: Part
  },

效果图:

1.png


2.png

总结:

  1. ?key=value 用$route.query.key 取值
  2. /值 提前在路由规则/path/:key 用$route.params.key 取值

8. Vue路由 - 重定向

了解: redirect

匹配path后, 强制切换到目标path

基本用法

  • 网页打开url默认hash值是/路径
  • redirect是设置要重定向到哪个路由路径 例如: 网页默认打开, 匹配路由"/", 强制切换到"/find"上
const routes = [
  {
    path: "/", // 默认hash值路径
    redirect: "/find" // 重定向到/find
    // 浏览器url中#后的路径被改变成/find-重新匹配数组规则
  }
]

总结:

  1. 强制重定向后, 还会重新来数组里匹配一次规则

9. Vue路由 - 404页面

了解

如果路由hash值, 没有和数组里规则匹配

默认给一个 404 页面

基本用法

路由最后, path匹配*(任意路径) – 前面不匹配就命中最后这个, 显示对应组件页面

  1. 创建 NotFound 页面
<template>
  <img src="../assets/404.png" alt="">
</template>

<script>
export default {

}
</script>

<style scoped>
    img{
        width: 100%;
    }
</style>
  1. 在main.js - 修改路由配置
import NotFound from '@/views/NotFound'

const routes = [
  // ...省略了其他配置
  // 404在最后(规则是从前往后逐个比较path)
  // 在配置规则的最底部写通配符 * 匹配所有路径
  // 兜底的规则, 当用户访问的路径都不匹配时, 就跳到这条规则
  {
    path: "*",
    component: NotFound
  }
]

总结:

  1. 如果路由未命中任何规则, 给出一个兜底的404页面

10. Vue路由 - 模式设置

了解

修改路由在地址栏的模式

基本用法

模式文档

hash路由例如: http://localhost:8080/#/home

history路由例如: http://localhost:8080/home (以后上线需要服务器端支持, 否则找的是文件夹)

11. Vue路由 - 编程式导航_跳转

了解

用JS代码来进行跳转

基本用法

this.$router.push({
    path: "路由路径", // 都去 router/index.js定义
    name: "路由名"
})
  1. main.js - 路由数组里, 给路由起名字
{
    path: "/find",
    name: "Find",
    component: Find
},
{
    path: "/my",
    name: "My123",
    component: My
},

    path: "/part",
    name: "Part",
    component: Part
},
  1. App.vue - 换成span 配合js的编程式导航跳转
<template>
  <div>
    <div class="footer_wrap">
      <span @click="btn('/find', 'Find')">发现音乐</span>
      <span @click="btn('/my', 'My123')">我的音乐</span>
      <span @click="btn('/part', 'Part')">朋友</span>
    </div>
    <div class="top">
      <router-view></router-view>
    </div>
  </div>
</template>

<script>
// 目标: 编程式导航 - js方式跳转路由
// 语法:
// this.$router.push({path: "路由路径"})
// this.$router.push({name: "路由名"})
// 注意:
// 虽然用name跳转, 但是url的hash值还是切换path路径值(例如: 点击我的音乐实现跳转时, url的hash值还是'/my')
// 场景:
// 方便修改: name路由名(在页面上看不见随便定义)
// path可以在url的hash值看到(尽量符合组内规范)
export default {
  methods: {
    btn(targetPath, targetName){
      // 方式1: path跳转
      this.$router.push({
        // path: targetPath,
        name: targetName
      })
    }
  }
};
</script>

12. Vue路由 - 编程式导航_传参

了解

JS 跳转路由, 传参

基本用法

语法 query / params 任选 一个

this.$router.push({
    path: "路由路径"
    name: "路由名",
    query: {
    	"参数名": 值
    }
    params: {
		"参数名": 值
    }
})

// 对应路由接收   $route.params.参数名   取值
// 对应路由接收   $route.query.参数名    取值

格外注意: 使用path会自动忽略params

App.vue

<template>
  <div>
    <div class="footer_wrap">
      <span @click="btn('/find', 'Find')">发现音乐</span>
      <span @click="btn('/my', 'My')">我的音乐</span>
      <span @click="oneBtn">朋友-小梦</span>
      <span @click="twoBtn">朋友-小牛</span>
    </div>
    <div class="top">
      <router-view></router-view>
    </div>
  </div>
</template>

<script>
// 目标: 编程式导航 - 跳转路由传参
// 方式1:
// params => $route.params.参数名
// 方式2:
// query => $route.query.参数名
// 重要: path会自动忽略params
// 推荐: name + query方式传参
// 注意: 如果当前url上"hash值和?参数"与你要跳转到的"hash值和?参数"一致, 爆出冗余导航的问题, 不会跳转路由
export default {
  methods: {
    btn(targetPath, targetName){
      // 方式1: path跳转
      this.$router.push({
        // path: targetPath,
        name: targetName
      })
    },
    oneBtn(){
      // 对象是无序属性的集合
      this.$router.push({
        path: '/part',
        // name: 'Find', // 一般情况不建议两个都写, 如果都写了, name 生效
        query: {
          name: "小梦",
          age: 18,
        },
        params: {  // 使用 path 时会被自动忽略
          username: '小梦牛'
        }
      })
    },
    twoBtn(){
      this.$router.push({
        name: 'Part',
        query: {
          name: '小牛'
        }
      })
    }
  }
};
</script>

13. Vue路由 - 组件缓存

了解

keep-alive -> 实现组件缓存效果

路由页面切换时, 都会执行哪些生命周期方法?

  • 消失: destroyed销毁流程
  • 出现: created初始化流程 - 所有代码重新执行
  1. 给3个子组件添加createddestroyed方法

  2. 进行测试切换效果

     *  路由切换时, 消失的页面, 会被销毁, 触发destroyed
     *  再切换回来, 重新创建, 所有代码重新执行, 效率不高
    
  3. 可以给 router-view 外面包裹 Vue自带的 keep-alive 标签即可

  4. 组件缓存是把相关信息存储在内存中

  5. 再次测试, 会发现, 组件被缓存起来了, destroyed 方法销毁流程不会执行

14. Vue路由 - 组件缓存_匹配缓存

了解

include / exclude, 区别缓存组件

基本用法

需求: 只缓存<发现音乐>页面

  1. 需要先给要区分的组件们, 设置name字段和值
<script>
    export default {
        name: '组件名'
    }
</script>
  1. 在keep-alive的时候, 使用 include / exclude 区分即可
    • include 包含哪些组件名需要缓存

      <keep-alive include="Find">
          <router-view></router-view>
      </keep-alive>
      
    • exclude 缓存哪些名字的组件

      注意: exclude后面字符串逗号后面不能有空格

      <keep-alive exclude="My,Part">
          <router-view></router-view>
      </keep-alive>
      

15. Vue路由 - 组件缓存_钩子函数

了解

使用keep-alive后, 新增了2个钩子函数

基本用法

组件不执行销毁/初始化创建的方法了, 如何知道组件被失去激活/激活呢?

  • activated --- 组件被激活状态
  • deactivated --- 组件被失去激活状态

生命周期函数:在被keep-alive包含的组件/路由中,会多出两个生命周期的钩子:activated 与 deactivated。

  1. activated钩子:在在组件第一次渲染时会被调用,之后在每次缓存组件被激活时调用。
  2. Activated钩子调用时机:** 第一次进入缓存路由/组件,在mounted后面,beforeRouteEnter守卫传给 next 的回调函数之前调用,并且给因为组件被缓存了,再次进入缓存路由、组件时,不会触发这些钩子函数,beforeCreate created beforeMount mounted 都不会触发

  1. deactivated钩子:**组件被停用(离开路由)时调用。
  2. deactivated钩子调用时机**:使用keep-alive就不会调用beforeDestroy(组件销毁前钩子)和destroyed(组件销毁),因为组件没被销毁,被缓存起来了,这个钩子可以看作beforeDestroy的替代,如果你缓存了组件,要在组件销毁的的时候做一些事情,可以放在这个钩子里,组件内的离开当前路由钩子beforeRouteLeave => 路由前置守卫 beforeEach =>全局后置钩子afterEach => deactivated 离开缓存组件 => activated 进入缓存组件(如果你进入的也是缓存路由)

16. 路由导航守卫

“导航”表示路由正在发生改变。 官网 : 路由导航守卫

了解

router.beforeEach

router.afterEach

router.beforeResolve

beforeEnter

beforeRouteEnter

beforeRouteUpdate (2.2 新增)

beforeRouteLeave

基本用法

1. 全局前置守卫 - router.beforeEach

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

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

router.beforeEach((to, from, next) => {
  //  to:要去哪个页面
  //  from:从哪里来
  //  next:它是一个函数。
  //     如果直接放行 next()
  //     如果要跳到其它页 next(其它页)
})

示例代码:

router.beforeEach(async(to, from, next) => {
  NProgress.start() // 开启进度条
  const token = store.state.user.token
  const userId = store.state.user.userInfo.userId
  console.log(token, to.path, from.path)
  if (token) {
    if (to.path === '/login') { // 有 token还要去login
      next('/')
      NProgress.done() // 关闭进度条
    } else { // 有 token,去其他页面,放行
      if (!userId) {
        await store.dispatch('user/getUserInfo')
      }
      next()
    }
  } else {
    if (to.path === '/login') { // 没有token,去login,放行
      next()
    } else {
      next('/login') // 没有token,去其他页面
      NProgress.done()
    }
  }
})

小结

  • router.beforeEach(回调(三个参数))
  • 导航守卫函数中,一定要调用next( )
  • to.path: to是一个路由对象,path表示路径,是它的一个属性

2. 全局后置钩子 - router.afterEach

router.afterEach 注册一个全局后置钩子:

你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:

router.afterEach((to, from) => {
 // ...
})

3. 全局解析守卫 - router.beforeResolve

在 2.5.0+ 你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。

4. 路由独享的守卫

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

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

5. 组件内的守卫

  • beforeRouteEnter
  • beforeRouteUpdate (2.2 新增)
  • beforeRouteLeave
const Foo = {
  template: `...`,
  beforeRouteEnter(to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate(to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }
}

完整的导航解析流程

  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter。
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter。
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

整理不易, 觉得写得还可以的, 多多点赞嘿嘿~