Vue学习笔记(五)

什么是路由

听到路由这个名词,第一个反应肯定是我们家里连 WiFi 的路由器,那么到底是什么是路由呢?

百度百科是这么说的

路由是指路由器从一个接口上收到数据包,根据数据包的目的地址进行定向并转发到另一个接口的过程。

简单来说就是路由器收到了一个数据包,根据这个数据包的目的地址,去找对应的另一个接口,然后把包给他。打个比方就是,你们小区因为疫情原因封了,送外卖的进不来,外卖只能送到门卫,然后门卫再根据外卖单子上的名字和电话找到你,把外卖给你。如果这里门卫就是一个路由器,他做的这件事,就叫路由

网络工程中的路由当然比这复杂多了,定向就不是看个简单地址就能找到那么简单,不过 Vue 里的路由相对要简单很多。Vue 里的路由器要做事情很简单,就是根据你的 url 也就是俗称的网址,来显示对应的组件

比如 url 是 localhost:8080/login ,我们就显示 Login 组件,localhost:8080/home 就显示 Home 组件。在Web开发中,通常路由是指根据URL分配到对应的处理程序,在 Vue 里就是分配到对应的组件

Vue Router 的安装及基本使用

其实把由于都是官方开发的,所以在环境配置上不能说和 Vuex 是一模一样,只能说是完全一致

老样子先安装 npm 包

npm i vue-router

vuex 是放在 store 里,因为是 store 来管理的,那么 vue-router 呢?自然是放在 router 里了,路由自然是路由器来管理了

在 src 目录下新建 router 文件夹,依旧是新建个 index.js

// src/router/index.js
import Vue from 'vue'
//引入VueRouter
import VueRouter from 'vue-router'

//使用插件
Vue.use(VueRouter)

//引入需要路由器来管理的组件,这里随便举个例子
import Login from '../components/Login'
import Home from '../components/Home'

//创建并导出 router实例对象,去管理这几组路由规则
export default new VueRouter({
    routes: [{
            path: '/login',
            component: Login
        },
        {
            path: '/home',
            component: Home
        }
    ]
})

然后在 main.js 中导入路由配置项

// src/main.js
...

//导入router
import router from './router'

...

//创建 Vue实例
new Vue({
	el:'#app',
	render: h => h(App),
	router //传入store配置项
})

然后你就会发现,地址栏的路径里多了个 #,那就说明你这个路由已经是可以用了,不过你输入之前配置的路径会发现并没有切换组件,这是为什么呢?这不废话,你还没告诉人家在哪里显示组件呢

你只需要在要显示路由控制的那几个组件的地方,放上一个

<router-view></router-view>

就可以了,路由器就会根据你的地址往这个里面塞地址对应的组件

路由跳转

声明式路由导航

现在你已经可以根据地址来显示对应的组件了,但是我们总不能每次都手动输入吧,当然你用 a 标签直接跳转对应地址也行,但是手动输入和 a 标签的跳转都是重新打开了个新界面了,而不是简单的切换一下 router-view 里的组件,这和我们想要的可不一样

所以官方提供了一个特制版的 a 标签——router-link

<router-link active-class="active" to="/login">login</router-link>

用法可以说和 a 标签基本相同,最后显示出来也就是个 a 标签,当然区别肯定是有的

首先最关键的当然是 router-link 并不会重新加载界面,而是直接切换组件,最简单的判断方法就是看浏览器左上角的刷新按钮有没有转,转了就是发了网络请求,重新加载了界面,没转就不会发网络请求,只是单纯的切换组件

还有几个不同,比如 a 标签的跳转链接是 href ,而 router-link 就是 to,我要跳转到哪,我就 to 哪

另外还有个方便的属性 active-class ,这个就是方便你控制样式的,当当前路径是要 to 的那一个时,这个 router-link 就会处于一个 active 状态,就会自动加上一个 active-class 所指定的这个 class

编程式路由导航

显然不能所有跳转都是点击 router-link 生成的 a 标签来跳转,必然还有一些时候需要自动跳转,所以我们可以通过代码来进行主动的跳转

在此之前,我们需要知道浏览器是有操作历史的,我们可以点击左上角的箭头来选择往前回到上一个地址,或者往后回来

所以我们这个跳转是有两种方式,一种是直接新加一个操作历史,另一个是取代当前的操作历史

正常情况我们默认都是第一种,如果所有跳转方式都是第二种的话,我们每一次跳转都会覆盖上一次跳转的操作记录,这样的话,你就会发现没有办法往前或往后跳了,因为操作记录至始至终就只有最新的一条

两种方式对应了两个 api

this.$router.push('home')        //对应第一种方式
this.$router.replace('hone')    //对应第二种方式

如果要让 router-link 也采用 replace 方式的话,只需要增加个属性 replace 即可

<router-link replace ></router-link>

除此之外还有三个api

this.$router.forward() //相当于浏览器左上角前进 -> 按钮
this.$router.back()    //相当于浏览器左上角后退 <- 按钮
this.$router.go()      //这个传数字,0 会刷新界面,-1 就是按一次 <-,1就是按一次 ->,以此类推

嵌套路由

我们地址是可以写很多级,路由显然不可能只有一级,路由是可以嵌套的,路由组件里还可以套路由组件,一直套娃下去

很简单,除了把 router-view 放到对应位置外,只需要修改一些路由配置即可

routes: [{
        path: '/login',
        component: Login,
    },
    {
        path: '/home',
        component: Home,
        children: [ //通过children配置子级路由
            {
                path: 'home1', //此处不需要写 /
                component: Home1
            },
            {
                path: 'home2', //此处不需要写 /
                component: Home2
            }
        ]
    }
]

现在只要访问 /home/home1 就会显示出 Home1 组件了

如果用 router-link 的话,同理

<router-link to="/home/home1">News</router-link>

路由传参

query 参数

路由是可以通过 url 地址来获取一些参数的,这个参数有两种,第一种就是 query

有点类似我们使用 get 请求的传参方式,就是通过一个 ? 后面跟参数,这种比较简单,直接把参数在路径上即可,不需要额外的配置

<!-- 字符串写法 -->
<router-link :to="/home/message?id=1234&msg=你好">跳转</router-link>

<!-- 对象写法 -->
<router-link
    :to="{
        path: '/home/message',
        query: {
            id: 1234,
            msg: '你好'
        }
    }"
>跳转</router-link>

要接收参数也简单,直接在路径对应的组件上找,注意,这里是路由不是路由器,路由器是所有组件共有的,只有一个,路由则是每个组件对应一个

this.$route.query.id
this.$route.query.msg

params 参数

第二种就是 params 参数,这种就是直接把参数放在路径中间,而不是单独指出来

因为是直接放在路径里,所以为了区分哪几级是路径,哪几级是参数,就需要实现配置好,每一级是什么

{
    path: '/home',
    component: Home,
    children: [{
        path: 'message/:id/:msg',
        component: Message
    }]
}

后面就和之前的差不多

<!-- 字符串写法 -->
<router-link :to="/home/message/1234/你好">跳转</router-link>

<!-- 对象写法 -->
<router-link
    :to="{
        path: '/home/message',
        params: {
            id: 1234,
            msg: '你好'
        }
    }"
>跳转</router-link>

到 Message 组件里接收

this.$route.params.id
this.$route.params.msg

命名路由

随着你路由嵌套一多,就会发现每次写路径,越写越长,是不是觉得很麻烦,官方很贴心的提供了方法,你可以不用这么一长串的路径,而是给它取个名字来代替

用法很简单,配置时,加个name,就可以代替对应的path

const router = new VueRouter({
  routes: [
    {
      path: '/user/:userId',
      name: 'user',
      component: User
    }
  ]
})

用 router-link 时只要把 path 换成 name 即可

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

用编程式的时候同理(我是不是忘了说编程式的也支持对象写法了

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

路由组件传参

当你通过路由传了一大堆参数时,你会不会感觉写一大堆 this.$route.params.xxx 或者 this.$route.query.xxx 很麻烦,写成 computed 来简化,也没简单多少

这时候你是不是想到了 vuex 的四个 map 函数?很遗憾的告诉你,路由它没有这东西,不过也可以通过类似的方法来简化操作

答案是用 props ,之前我们使用 props 是接收父组件传过来的东西,实际上它还可以接收路由传过来的参数,只不过要稍微配置下

这个配置有三种方式

  • 布尔模式,会把该组件的所有 params 参数通过 props 传给该组件
  • 对象模式,将对象里定义的参数,通过 props 传给该组件,因为是写死固定的参数,很少用到
  • 函数模式,返回值要写成一个对象,然后通过 props 传给该组件,函数的默认参数是该组件的路由,所以可以拿到 query 和 params 参数
{
    path: '/home',
    component: Home,
    children: [{
        path: 'message/:id/:msg',
        component: Message,
        // props : true //布尔模式
        // props:{a:900} //对象模式
        props(route) { //函数模式
            return {
                id: route.query.id,
                msg: route.query.msg
            }
        }
    }]
}

路由守卫

官方文档里被翻译为导航守卫,但导航守卫叫起来怪怪的,还是路由守卫叫起来比较舒服

官方解释为

“导航”表示路由正在发生改变。

其实吧这个路由守卫,就是几个关于路由的生命周期钩子

正如官方所说,所有路由守卫都发生在路由改变的时候,因此一般守卫基本都接受这几个参数

  • to: Route: 即将要进入的目标路由对象
  • from: Route: 当前导航正要离开的路由
  • next: Function: 放行,类似于 Promise 的 resolve

全局前置守卫

//全局前置守卫会在每次路由切换前执行,初始化时也会执行一次
router.beforeEach((to, from, next) => {
    if (to.meta.isAuth) { //判断当前路由是否需要进行权限控制
        if (xxxx) { //权限控制的具体规则
            next() //放行
        } else {
            alert('你不能过去')
        }
    } else {
        next() //放行
    }
})

路由配置时还可以增加 meta 属性,值是个对象,没有定义 meta,就是空对象

因为是全局守卫,所有路由跳转都会生效,所以一般都需要先判断一下该次路由跳转要不要管,可以通过 to 或 from 的 name 或 path,来判断,也可以通过定义个meta,里面定义是否需要权限控制之类的

全局后置守卫

//全局后置守卫会每次路由切换后执行,初始化时也会执行一次
router.afterEach((to, from) => {
    if (to.meta.title) {
        document.title = to.meta.title //修改网页的title
    } else {
        document.title = 'vue_test'
    }
})

全局守卫其实还有一个新增的全局解析守卫,不过官方文档上也没多提,一般用不到,可以忽略

除此之外要注意一点,全局守卫因为是全局的,因此是单独定义的

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

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

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

export default router

路由独享守卫

刚才说了全局守卫,那么当然得有局部的了,独享守卫就是直接定义在路由配置中了,不过这个独享守卫只对当前路由生效,且没有后置守卫,顾名思义,beforeEnter 发生在进入当前路由前,其余用法和全局前置一样,

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

组件内守卫

除此之外,我们还有组件内守卫,这个守卫顾名思义是放在组件里面的,用法依旧类似全局前置守卫

可以说除了全局守卫,其他的都没有后置守卫(毕竟该干的都干完了,后置你还守卫啥

还需要注意一点,就是组件内守卫只会在通过路由规则进入或离开该组件时才会触发(虽然其实也没有其他方法)

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`
  }
}

History 模式

有没有发现一点,自从我们用了路由,路径中间就有个 # 号,从来没有消失过,如果删掉,还会出问题

这个 # 号其实叫做 hash ,# 后面的就是 hash 值,这里的 hash 和数据加密的 hash 没啥关系,只是个前端的概念,这里的 hash 值是只被用于前端的,这也是为什么我们路径虽然在变,但并没有发送网络请求,页面并没有加载,后端收到的路径其实只有 # 号前面的,# 号后面的只有前端能看到,也只对前端有用

有 # 号的这个模式就叫 Hash 模式,但有时候你可能会觉得这个 # 号比较丑,不想留在地址栏上,HTML5 开始,就有了 History 模式,可以让你去掉这个 #

Vue 的路由自然也是支持使用这个 History 模式

方法很简单,你只需要在路由配置时加一个 mode 属性即可,然后你的路径就和普通的路径一样了,没有那个 # 号了

const router = new VueRouter({
  mode: 'history',
  routes: [...]
})

然后你就会发现,如果你手动输入路径会出问题,因为你直接输入路径,浏览器不知道从哪开始是请求的路径,哪里开始是前端路由用的了,直接一股脑的都给后端发过去了,甚至你刷新一下页面,也凉了

那怎么办呢,那显然没办法,这剩下的事就是后端干的了,让后端自己去判断发的这坨路径,哪些是他要的,哪些是前端用的