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
函数也不会改变导航本身
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>
效果:
通过上面可以得出以下结论:
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】