vue-router(導航守衛,路由元信息,獲取數據)
之前泄露兩篇有關vue-router博客:
一、導航守衛
當做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、組件內的守衛
最后,你可以在路由組件內直接定義以下路由導航守衛
beforeRouteEnterbeforeRouteUpdate(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】
