路由守卫
当导航发生改变的时候,vue-router 会在多个不同的地方调用指定的函数,也就是与路由有关的生命周期函数,也称为:路由守卫
- 组件内守卫
- 路由独享守卫:写在某个路由配置里
- 全局守卫:写在整个路由对象里
一、组件内守卫
定义在组件内的与路由有关的生命周期函数(守卫)
- beforeRouteEnter
- beforeRouteUpdate
- beforeRouteLeave
1.1 beforeRouteEnter
当路由解析完成,并中指定的组件渲染之前(组件 beforeCreate
之前),不能这里通过 this
访问组件实例,需要通过 next
回调来进行调用 ,回调后的vm即实例对象。
beforeRouteEnter (to, from, next) {
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
next(vm => {
// vm...
})
}
1.2 beforeRouteUpdate
在当前路由改变,但是该组件被复用时调用
组件被复用时调用(更新):如表单填写后离开页面时询问是否要提交页面等需求
beforeRouteUpdate (to, from, next) {
// 可以访问组件实例 `this`
},
1.3 beforeRouteLeave
导航离开该组件的对应路由时调用
beforeRouteLeave (to, from, next) {
// 可以访问组件实例 `this`
}
二、路由守卫参数
2.1 to
即将要进入的目标 路由对象($route)
2.2 from
当前导航正要离开的路由对象($route)
2.3 next
路由确认回调函数,类似 Promise 中的 resolve 函数,一定要确保调用 next 函数,但是后续的导航行为将依赖 next 方法的调用参数
next()
: 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。next()表示允许继续往下执行next(false)
: 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到from
路由对应的地址next('/') 或者 next({ path: '/' })
: 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航
示例:组件内守卫实现商品价格排序(解决组件复用性问题:地址栏变化了,页面没有刷新)
//Home.vue
<template>
<div class="home">
<h2>商品列表——首页</h2>
<!-- 使用v-model 绑定排序,并使用 watch进行监听-->
<select v-model="sort">
<option value="desc">从高到低</option>
<option value="asc">从低到高</option>
</select>
<ul class="item-list">
<li class="head">
<span>名称</span>
<span>品牌</span>
<span>价格</span>
<span>操作</span>
</li>
<li v-for="item of items" :key="item.id">
<span>{{item.name}}</span>
<span>{{item.vendor}}</span>
<span>{{item.price|RMB}}</span>
<span>
<!-- 注意:用v-bind方式,则属性值里必须是表达式-->
<!-- <router-link :to="`/item/${item.id}`">查看详情</router-link> -->
<!-- 以动态路由形式赋值,注意一定要以v-bind形式进行绑定,后台路由需要设置name:'item' -->
<router-link :to="{name: 'item', params:{id: item.id}}">查看详情</router-link>
</span>
</li>
</ul>
</div>
</template>
<script>
import axios from "axios";
import RMB from "@/filter/RMB";
export default {
name: "Home",
// 注册过滤器
filters: {
RMB
},
// 注意是在created生命周期中发送请求(组件渲染完成后)
data() {
return {
items: [],
sort: "desc"
};
},
async created() {
// 注意this.sort获取位置,是在组件渲染完后立即赋值,否则有可能没有获取到,从而产生异步问题
this.sort = this.$route.query.sort || this.sort;
// 注意请求需要使用async await
await this.getItems();
},
// 使用watch监听组件变化,注意watch是对象
watch: {
sort() {
// 监听到sort发生变化后重新发起请求(但是这样不会在url上带上sort=desc不利于分享状态)
// await this.getItems();
// 然后通过编程式导航进行跳转(可以拼接字符串,也可以对象形式传参)
// this.$router.push('/?sort=' + this.sort);
this.$router.push({
// 此处name对应路由中Home组件的路由name
name: "home",
// 此处为地址栏上地址加上queryString
query: {
sort: this.sort
}
});
},
// 如果使用了路由守卫就不用监听$route
// 因为vue组件的复用性(vue在地址改变后,发现还是组件还是之前的组件,就不会进行重新创建),光使用queryString+this.$router.push还无法真正刷新页面
// async $route() {
// // 监听到变化时再发一次请求
// await this.getItems();
// }
},
methods:{
async getItems(){
await axios({
// 通过axios发起异步代理请求
url: "/api/items",
// 此处为发送给后台请求地址加上queryString
params: {
sort: this.sort
}
}).then(res => {
// 将获取到的值设置到data中
this.items = res.data;
});
}
},
// 组件内守卫,在特定组件中使用
// 当路由解析完成,并中指定的组件渲染之前
// next('/') 或者 next({ path: '/' }) : 跳转到一个不同的地址
beforeRouteEnter(to, from, next){
console.log("beforeRouteEnter");
// 此处处理在组件渲染之前,先跳转到其他地址
// 注意next之前不能调用this获取实例
console.log(this);//undefined
// next(_this=>{
// console.log(_this);//此处_this即为组件实例
// });
next();
},
// 组件被复用时调用(更新):如表单填写后离开页面时询问是否要提交页面等需求
beforeRouteUpdate(to, from, next){
// 注意不是使用了路由守卫这里就不需要this.$router.push(),这是为了编程的方式来导航(跳转)
// 此处使用了this.$router.push()方法,阻隔了组件的复用性,所以组件复用时就会调用
console.log("beforeRouteUpdate");
// 更新时,只有此处next()确认后才能生效
// 使用路由守卫发起请求,不再监听$route对象
this.getItems();
next();
},
// 导航离开该组件时调用
beforeRouteLeave(to, from, next){
console.log("beforeRouteLeave");
next();
}
};
</script>
<style>
ul {
margin: 0;
padding: 0;
}
li {
list-style: none;
}
.item-list li {
padding: 10px;
display: flex;
justify-content: space-between;
height: 30px;
line-height: 30px;
border-bottom: 1px dotted #333;
}
.item-list li.head {
font-weight: bold;
}
.item-list li span {
min-width: 200px;
}
</style>
三、路由独享守卫
可以在路由配置上直接定义 beforeEnter
守卫,相对来说,应用不多
const router = new VueRouter(
{
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
}
)
四、全局守卫
全局守卫是注册在 router 对象(new VueRouter({...}))上的
-
beforeEach
-
beforeResolve
-
afterEach
4.1 beforeEach
当一个导航触发时,全局前置守卫按照创建顺序调用
router.beforeEach((to, from, next) => {
// ...
})
4.2 beforeResolve
在所有组件内守卫和异步路由组件被解析之后被调用
router.beforeResolve((to, from, next) => {
// ...
})
4.3 afterEach
导航被确认后调用
router.afterEach((to, from) => {
// ...
})
因为导航已经被确认,所以没有
next
示例, 全局守卫实现鉴权:
//Login.vue
<template>
<div>
Login
</div>
</template>
<script>
export default {
name: 'Login'
}
</script>
<style>
</style>
//route.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home.vue'
import About from '@/components/About.vue'
import Item from '@/components/Item.vue'
import Login from '@/components/Login.vue'
Vue.use(Router)
const appRouter = new Router({
mode: 'history',
routes: [{
name: "home",
path: '/',
component: Home
},
{
name: "about",
path: '/about',
component: About,
// 利用路由元信息进行鉴权
meta: {
requiresAuth: true
}
},
{
// 页面以动态路由形式赋值时:to='{name: "item", params:{id: item.id}}',需要给路由设置name才能赋值
name: 'item',
// :id配置在route配置时
path: '/item/:id',
component: Item,
// 利用路由元信息进行鉴权
meta: {
// requiresAuth: true
}
},
{
name: "login",
path: '/Login',
component: Login
},
]
});
// 全局守卫(处理如鉴权等需求)
// 当一个导航触发时,全局前置守卫按照创建顺序调用
let user = { id: 1 }
appRouter.beforeEach((to, from, next) => {
// 点击About和Item(查看详情)时,需要进行鉴权
console.log(to, from);
if (["about", "item"].includes(to.name) || user.id === 0) {
next({ name: 'login' });
}
})
// 在所有组件内守卫和异步路由组件被解析之后被调用
appRouter.beforeResolve((to, from, next) => {
next();
})
// 导航被确认后调用:因为导航已经被确认,所以没有 next
appRouter.afterEach((to, from) => {
})
export default appRouter;