VueJs(12)---vue-router(导航守卫,路由元信息)

时间:2023-12-14 17:57:32

vue-router(导航守卫,路由元信息,获取数据)

之前泄露两篇有关vue-router博客:

VueJs(10)---vue-router(进阶1)

VueJs(11)---vue-router(进阶2)

一、导航守卫

当做Vue-cli项目的时候感觉在路由跳转前做一些验证,比如登录验证,是网站中的普遍需求,这个时候就需要导航守卫,你可以在页面跳转前做逻辑判断,时候跳转,跳转到指定页面。

       (1)全局的(beforeEach,afterEach,beforeResolve),

       (2)单个路由独享的(beforeEnter),

  (3)组件级的(beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave)。

1、全局守卫

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

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

router.beforeEach((to, from, next) => {
// 业务逻辑处
})

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

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

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

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

   next(): 进行管道中的下一个钩子。这个是必须要的,否在无法完成跳转。

   next(false): 中断当前的导航。就相当于点击之后不会跳转到指定页面,就相当于没有写next()一样。

   next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,

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

     确保要调用 next 方法,否则钩子就不会被 resolved。

全局后置钩子

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

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

路由独享的守卫

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

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

2、组件内的守卫

最后,你可以在路由组件内直接定义以下路由导航守卫

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

   beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。

不过,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。

beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}

注意 beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdate 和 beforeRouteLeave来说,this 已经可用了,所以不支持传递回调,因为没有必要了。

beforeRouteUpdate (to, from, next) {
// just use `this`
this.name = to.params.name
next()
}

这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。

beforeRouteLeave (to, from , next) {
const answer = window.confirm('你确定要退出吗')
if (answer) {
next()
} else {
next(false)
}
}

下面用一个小案例说明:

<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script> <div id="app">
<button type="button" @click="fooClick">/user/foo</button>
<button type="button" @click="profileClick">/user/foo/profile</button>
<button type="button" @click="postsClick">/user/foo/posts</button> <router-view></router-view>
</div>
<script>
//定义了三个组件
const UserHome = { template: '<div>Home</div>'}
const UserProfile = {template: '<div>Profile</div>' }
const UserPosts = {template: '<div>Posts</div>'} //匹配了三个路由
const router = new VueRouter({
routes: [{
path: '/user/foo',
component: UserHome,
beforeEnter: (to, from, next) => {
console.log('进入UserHome组件');
next();
}
}, {
path: '/user/foo/profile',
component: UserProfile,
beforeEnter: (to, from, next) => {
console.log('进入profile组件');
next();
}
}, {
path: '/user/foo/posts',
component: UserPosts,
beforeEnter: (to, from, next) => {
console.log('UserPosts组件');
//这里设置flase说明就算路径为'/user/foo/posts',也不会进入UserPosts组件
next(false);
}
}]
})
//设置全局导航守卫
router.beforeEach((to, from, next) => {
console.log('进入全局守卫导航');
console.log('进入路由路径:' + to.path);
console.log('离开路由路径:' + from.path);
next();
})
const app = new Vue({
el: '#app',
router,
data: {
foo: '/user/foo',
profile: '/user/foo/profile',
posts: '/user/foo/posts'
},
methods: {
fooClick: function(event) {
this.$router.push({
path: this.foo
});
},
profileClick: function(event) {
this.$router.push({
path: this.profile
});
},
postsClick: function(event) {
this.$router.push({
path: this.posts
});
} }
})
</Script>

效果:

VueJs(12)---vue-router(导航守卫,路由元信息)

通过上面可以得出以下结论:

1、全局守卫会比组件守卫先执行

2.全局守卫第一次加载页面to和from路径都是“/”

3.每一次路径改变都会调用全局组件,路径不变是不会调用全局守卫,也不会在调用组件守卫

4.我在/user/foo/posts路径设置为next(flase)是,当点击它是并没有跳转UserPosts组件,页面显示的还是profile说明跳转失败。

二、路由元信息(meta)

为什么会有路由元信息这个东西?首先要知道它到底有什么作用。

我们在做网站登录验证的时候,可以使用到beforeEach 钩子函数进行验证操作,如下面代码 ,如果页面path为’/goodsList’,那么就让它跳转到登录页面,实现了验证登录。

router.beforeEach((to, from, next) => {
if (to.path === '/goodsList') {
next('/login')
} else
next()
})

如果需要登录验证的网页多了怎么办?

1.这里是对比path。如果需要验证的网页很多,那么在if条件里得写下所有的路由地址,将会是非常麻烦的一件事情。

2.因为路由是可以嵌套的。有’/goodsList’,那么可能会有’/goodsList/online’,再或者还有’/goodsList/offline’、’/goodsList/audit’、’/goodsList/online/edit’等等。

如果像刚才例子中这样对比(to.path === ‘/goodsList’),就非常单一,其他的路径压根不会限制(验证)到,照样能正常登陆!因为每个to.path根本不一样。

我们所理想的就是把’/goodsList’限制了,其他所有的以’/goodsList’开头的那些页面都给限制到!

to Route: 即将要进入的目标 路由对象 
我们打印一下to

它有很多属性,有 
  - fullPath 
  - hash 
  - matched 
  - meta 
  - name 
  - params 
  - path 
  - query

其中有个属性,matched,就是匹配了的路由,我们打印出来,这个是个数组。它的第一项就是{path: “/goodslist”},一直到最为具体的当前path (例如:{path: “/goodslist/online/edit”})

这里可以循环matched这个数组,看每一项的path 有没有等于’/goodsList’,只要其中一个有,那么就让它跳转到登录状态

router.beforeEach((to, from, next) => {
if (to.matched.some(function (item) {
return item.path == '/goodslist'
})) {
next('/login')
} else
next()
})

那么这里只是对goodsList进行验证判断,且限制的还是path,就相当于把goodsList下面都给限制了,但有些东西是不需要登陆直接可以显示在页面的,比如:资讯信息,广告信息。这么做不就不可行了。用path来做限制似乎有点不好用。轮到主角登场了

meta字段(元数据)

直接在路由配置的时候,给每个路由添加一个自定义的meta对象,在meta对象中可以设置一些状态,来进行一些操作。用它来做登录校验再合适不过了

{
path: '/actile',
name: 'Actile',
component: Actile,
meta: {
login_require: false
},
},
{
path: '/goodslist',
name: 'goodslist',
component: Goodslist,
meta: {
login_require: true
},
children:[
{
path: 'online',
component: GoodslistOnline
}
]
}

这里我们只需要判断item下面的meta对象中的login_require是不是true,就可以做一些限制了

router.beforeEach((to, from, next) => {
if (to.matched.some(function (item) {
return item.meta.login_require
})) {
next('/login')
} else
next()
})

meta还可以放其它信息,比如可以存储该路由相关信息(例如:设置每个路由的title,取路由的title设置为选项卡的标题)

{
path: '/router2',
name: 'router2',
component:router2,
meta:{
title:"积分模块"
}
}
// 全局前置守卫
router.beforeEach((to,from,next) => {
console.log(to);
console.log(from);
if(to.meta.title) {
document.title = to.meta.title;
} else {
document.title = '我是默认的title'
}
next();
});

三、获取数据

有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。

我们可以通过两种方式来实现:

(1)导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示『加载中』之类的指示。

(2)导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。

1、导航完成后获取数据

当你使用这种方式时,我们会马上导航和渲染组件,然后在组件的 created 钩子中获取数据。这让我们有机会在数据获取期间展示一个 loading 状态,还可以在不同视图间展示不同的 loading 状态。

假设我们有一个 Post 组件,需要基于 $route.params.id 获取文章数据:

<template>
<div class="post">
<div class="loading" v-if="loading">
Loading...
</div> <div v-if="error" class="error">
{{ error }}
</div> <div v-if="post" class="content">
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
</div>
</div>
</template>
export default {
data () {
return {
loading: false,
post: null,
error: null
}
},
created () {
// 组件创建完后获取数据,
// 此时 data 已经被 observed 了
this.fetchData()
},
watch: {
// 如果路由有变化,会再次执行该方法
'$route': 'fetchData'
},
methods: {
fetchData () {
this.error = this.post = null
this.loading = true
// replace getPost with your data fetching util / API wrapper
getPost(this.$route.params.id, (err, post) => {
this.loading = false
if (err) {
this.error = err.toString()
} else {
this.post = post
}
})
}
}
}
(err, post) =>是ES6写法,意思大概就是function(err, post){}大括号就相当于方法类逻辑。

2、在导航完成前获取数据

通过这种方式,我们在导航转入新的路由前获取数据。我们可以在接下来的组件的 beforeRouteEnter 守卫中获取数据,当数据获取成功后只调用 next 方法。

export default {
data () {
return {
post: null,
error: null
}
},
beforeRouteEnter (to, from, next) {
getPost(to.params.id, (err, post) => {
next(vm => vm.setData(err, post))
})
},
// 路由改变前,组件就已经渲染完了
// 逻辑稍稍不同
beforeRouteUpdate (to, from, next) {
this.post = null
getPost(to.params.id, (err, post) => {
this.setData(err, post)
next()
})
},
methods: {
setData (err, post) {
if (err) {
this.error = err.toString()
} else {
this.post = post
}
}
}
}

在为后面的视图获取数据时,用户会停留在当前的界面,因此建议在数据获取期间,显示一些进度条或者别的指示。如果数据获取失败,同样有必要展示一些全局的错误提醒。

想太多,做太少,中间的落差就是烦恼。想没有烦恼,要么别想,要么多做。上尉【1】