VueJs(12)---vue-router(導航守衛,路由元信息,獲取數據)


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>

效果:

通過上面可以得出以下結論:

     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】

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM