Vue的鉤子函數-守衛篇

前言

說到Vue的鉤子函數,可能很多人只停留在一些很簡單常用的鉤子(created,mounted),而且對於裏面的區別,什麼時候該用什麼鉤子,並沒有仔細的去研究過,且Vue的生命週期在面試中也算是比較高頻的考點,那麼該如何回答這類問題,讓人有眼前一亮的感覺呢…


Vue-Router導航守衛:

有的時候,我們需要通過路由來進行一些操作,比如最常見的登錄權限驗證,當用戶滿足條件時,才讓其進入導航,否則就取消跳轉,並跳到登錄頁面讓其登錄。

爲此我們有很多種方法可以植入路由的導航過程:全局的, 單個路由獨享的, 或者組件級的,推薦優先閱讀路由文檔

全局守衛

vue-router全局有三個守衛:

  1. router.beforeEach 全局前置守衛 進入路由之前
  2. router.beforeResolve 全局解析守衛(2.5.0+) 在beforeRouteEnter調用之後調用
  3. router.afterEach 全局後置鉤子 進入路由之後

使用方法

// main.js 入口文件
import router from './router'; // 引入路由
router.beforeEach((to, from, next) => { 
  next();
});
router.beforeResolve((to, from, next) => {
  next();
});
router.afterEach((to, from) => {
  console.log('afterEach 全局後置鉤子');
});

to,from,next 這三個參數:

to和from是將要進入和將要離開的路由對象,路由對象指的是平時通過this.$route獲取到的路由對象。

next:Function 這個參數是個函數,且必須調用,否則不能進入路由(頁面空白)。

  • next() 進入該路由。
  • next(false): 取消進入路由,url地址重置爲from路由地址(也就是將要離開的路由地址)。
  • next 跳轉新路由,當前的導航被中斷,重新開始一個新的導航。

    我們可以這樣跳轉:next('path地址')或者next({path:''})或者next({name:''})
    且允許設置諸如 replace: true、name: 'home' 之類的選項
    以及你用在router-link或router.push的對象選項。
    

路由獨享守衛

如果你不想全局配置守衛的話,你可以爲某些路由單獨配置守衛:

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => { 
        // 參數用法什麼的都一樣,調用順序在全局前置守衛後面,所以不會被全局守衛覆蓋
        // ...
      }
    }
  ]
})

路由組件內的守衛:

  1. beforeRouteEnter 進入路由前
  2. beforeRouteUpdate (2.2) 路由複用同一個組件時
  3. beforeRouteLeave 離開當前路由時

文檔中的介紹:

beforeRouteEnter (to, from, next) {
  // 在路由獨享守衛後調用 不!能!獲取組件實例 `this`,組件實例還沒被創建
},
beforeRouteUpdate (to, from, next) {
  // 在當前路由改變,但是該組件被複用時調用 可以訪問組件實例 `this`
  // 舉例來說,對於一個帶有動態參數的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候,
  // 由於會渲染同樣的 Foo 組件,因此組件實例會被複用。而這個鉤子就會在這個情況下被調用。
},
beforeRouteLeave (to, from, next) {
  // 導航離開該組件的對應路由時調用,可以訪問組件實例 `this`
}

beforeRouteEnter訪問this

因爲鉤子在組件實例還沒被創建的時候調用,所以不能獲取組件實例 this,可以通過傳一個回調給next來訪問組件實例

但是回調的執行時機在mounted後面,所以在我看來這裏對this的訪問意義不太大,可以放在created或者mounted裏面。

beforeRouteEnter (to, from, next) {
console.log('在路由獨享守衛後調用');
  next(vm => {
    // 通過 `vm` 訪問組件實例`this` 執行回調的時機在mounted後面,
  })
}

beforeRouteLeave:

導航離開該組件的對應路由時調用,我們用它來禁止用戶離開,比如還未保存草稿,或者在用戶離開前,將setInterval銷燬,防止離開之後,定時器還在調用。

beforeRouteLeave (to, from , next) {
  if (文章保存) {
    next(); // 允許離開或者可以跳到別的路由 上面講過了
  } else {
    next(false); // 取消離開
  }
}

關於鉤子的一些知識:

路由鉤子函數的錯誤捕獲

如果我們在全局守衛/路由獨享守衛/組件路由守衛的鉤子函數中有錯誤,可以這樣捕獲:

router.onError(callback => { 
// 2.4.0新增 並不常用,瞭解一下就可以了 
  console.log(callback, 'callback');
});

在路由文檔中還有更多的實例方法:動態添加路由等,有興趣可以瞭解一下。

跳轉死循環,頁面永遠空白

我瞭解到的,很多人會碰到這個問題,來看一下這段僞代碼:

router.beforeEach((to, from, next) => {
  if(登錄){
     next()
  }else{
      next({ name: 'login' }); 
  }
});

看邏輯貌似是對的,但是當我們跳轉到login之後,因爲此時還是未登錄狀態,所以會一直跳轉到login然後死循環,頁面一直是空白的,所以:我們需要把判斷條件稍微改一下。

if(登錄 || to.name === 'login'){ next() } // 登錄,或者將要前往login頁面的時候,就允許進入路由

全局後置鉤子的跳轉:

文檔中提到因爲router.afterEach不接受next函數所以也不會改變導航本身,意思就是隻能當成一個鉤子來使用,但是我自己在試的時候發現,我們可以通過這種形式來實現跳轉:

// main.js 入口文件
import router from './router'; // 引入路由
router.afterEach((to, from) => {
  if (未登錄 && to.name !== 'login') {
    router.push({ name: 'login' }); // 跳轉login
  }
});

額,通過router.beforeEach 也完全可以實現且更好,我就騷一下。

完整的路由導航解析流程(不包括其他生命週期):

  1. 觸發進入其他路由。
  2. 調用要離開路由的組件守衛beforeRouteLeave
  3. 調用局前置守衛:beforeEach
  4. 在重用的組件裏調用 beforeRouteUpdate
  5. 調用路由獨享守衛 beforeEnter
  6. 解析異步路由組件。
  7. 在將要進入的路由組件中調用beforeRouteEnter
  8. 調用全局解析守衛 beforeResolve
  9. 導航被確認。
  10. 調用全局後置鉤子的 afterEach 鉤子。
  11. 觸發DOM更新(mounted)。
  12. 執行beforeRouteEnter 守衛中傳給 next 的回調函數

你不知道的keep-alive[我猜你不知道]

在開發Vue項目的時候,大部分組件是沒必要多次渲染的,所以Vue提供了一個內置組件keep-alive緩存組件內部狀態,避免重新渲染文檔在這裏

文檔:和 <transition>相似,<keep-alive> 是一個抽象組件:它自身不會渲染一個 DOM 元素,也不會出現在父組件鏈中。

用法:

緩存動態組件:

<keep-alive>包裹動態組件時,會緩存不活動的組件實例,而不是銷燬它們,此種方式並無太大的實用意義。

<!-- 基本 -->
<keep-alive>
  <component :is="view"></component>
</keep-alive>

<!-- 多個條件判斷的子組件 -->
<keep-alive>
  <comp-a v-if="a > 1"></comp-a>
  <comp-b v-else></comp-b>
</keep-alive>

緩存路由組件:

使用keep-alive可以將所有路徑匹配到的路由組件都緩存起來,包括路由組件裏面的組件,keep-alive大多數使用場景就是這種。

<keep-alive>
    <router-view></router-view>
</keep-alive>

生命週期鉤子:

這篇既然是Vue鉤子函數的專場,那肯定要扣題呀~

在被keep-alive包含的組件/路由中,會多出兩個生命週期的鉤子:activated 與 deactivated

文檔:在 2.2.0 及其更高版本中,activated 和 deactivated 將會在 樹內的所有嵌套組件中觸發。

activated在組件第一次渲染時會被調用,之後在每次緩存組件被激活時調用

activated調用時機:

第一次進入緩存路由/組件,在mounted後面,beforeRouteEnter守衛傳給 next 的回調函數之前調用:

beforeMount=> 如果你是從別的路由/組件進來(組件銷燬destroyed/或離開緩存deactivated)=>
mounted=> activated 進入緩存組件 => 執行 beforeRouteEnter回調

因爲組件被緩存了,再次進入緩存路由/組件時,不會觸發這些鉤子

// beforeCreate created beforeMount mounted 都不會觸發。

所以之後的調用時機是:

組件銷燬destroyed/或離開緩存deactivated => activated 進入當前緩存組件 
=> 執行 beforeRouteEnter回調
// 組件緩存或銷燬,嵌套組件的銷燬和緩存也在這裏觸發

deactivated:組件被停用(離開路由)時調用

使用了keep-alive就不會調用beforeDestroy(組件銷燬前鉤子)和destroyed(組件銷燬),因爲組件沒被銷燬,被緩存起來了

這個鉤子可以看作beforeDestroy的替代,如果你緩存了組件,要在組件銷燬的的時候做一些事情,你可以放在這個鉤子裏。

如果你離開了路由,會依次觸發:

組件內的離開當前路由鉤子beforeRouteLeave =>  路由前置守衛 beforeEach =>
全局後置鉤子afterEach => deactivated 離開緩存組件 => activated 進入緩存組件(如果你進入的也是緩存路由)
// 如果離開的組件沒有緩存的話 beforeDestroy會替換deactivated 
// 如果進入的路由也沒有緩存的話  全局後置鉤子afterEach=>銷燬 destroyed=> beforeCreate等

那麼,如果我只是想緩存其中幾個路由/組件,那該怎麼做?

緩存你想緩存的路由:

Vue2.1.0之前:

想實現類似的操作,你可以:

  1. 配置一下路由元信息
  2. 創建兩個keep-alive標籤
  3. 使用v-if通過路由元信息判斷緩存哪些路由。

    <keep-alive>
        <router-view v-if="$route.meta.keepAlive">
            <!--這裏是會被緩存的路由-->
        </router-view>
    </keep-alive>
    <router-view v-if="!$route.meta.keepAlive">
        <!--因爲用的是v-if 所以下面還要創建一個未緩存的路由視圖出口-->
    </router-view>
    //router配置
    new Router({
      routes: [
        {
          path: '/',
          name: 'home',
          component: Home,
          meta: {
            keepAlive: true // 需要被緩存
          }
        },
        {
          path: '/:id',
          name: 'edit',
          component: Edit,
          meta: {
            keepAlive: false // 不需要被緩存
          }
        }
      ]
    });
    

Vue2.1.0版本之後:

使用路由元信息的方式,要多創建一個router-view標籤,並且每個路由都要配置一個元信息,是可以實現我們想要的效果,但是過於繁瑣了點。

幸運的是在Vue2.1.0之後,Vue新增了兩個屬性配合keep-alive來有條件地緩存 路由/組件。

新增屬性:

  • include:匹配的 路由/組件 會被緩存
  • exclude:匹配的 路由/組件 不會被緩存

includeexclude支持三種方式來有條件的緩存路由:採用逗號分隔的字符串形式,正則形式,數組形式。

正則和數組形式,必須採用v-bind形式來使用。

緩存組件的使用方式

<!-- 逗號分隔字符串 -->
<keep-alive include="a,b">
  <component :is="view"></component>
</keep-alive>

<!-- 正則表達式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
  <component :is="view"></component>
</keep-alive>

<!-- 數組 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
  <component :is="view"></component>
</keep-alive>

但更多場景中,我們會使用keep-alive來緩存路由

<keep-alive include='a'>
    <router-view></router-view>
</keep-alive>

匹配規則:

  1. 首先匹配組件的name選項,如果name選項不可用。
  2. 則匹配它的局部註冊名稱。 (父組件 components 選項的鍵值)
  3. 匿名組件,不可匹配

比如路由組件沒有name選項,並且沒有註冊的組件名。

  1. 只能匹配當前被包裹的組件,不能匹配更下面嵌套的子組件

比如用在路由上,只能匹配路由組件的name選項,不能匹配路由組件裏面的嵌套組件的name選項。

  1. 文檔:<keep-alive>不會在函數式組件中正常工作,因爲它們沒有緩存實例。
  2. exclude的優先級大於include

也就是說:當includeexclude同時存在時,exclude生效,include不生效。

<keep-alive include="a,b" exclude="a">
  <!--只有a不被緩存-->
  <router-view></router-view>
</keep-alive>

當組件被exclude匹配,該組件將不會被緩存,不會調用activated 和 deactivated


組件生命週期鉤子:

關於組件的生命週期,是時候放出這張圖片了:

這張圖片已經講得很清楚了,很多人這部分也很清楚了,大部分生命週期並不會用到,這裏提一下幾點:

  1. ajax請求最好放在created裏面,因爲此時已經可以訪問this了,請求到數據就可以直接放在data裏面。

    這裏也碰到過幾次,面試官問:ajax請求應該放在哪個生命週期。

  2. 關於dom的操作要放在mounted裏面,在mounted前面訪問dom會是undefined

  3. 每次進入/離開組件都要做一些事情,用什麼鉤子:

  • 不緩存:

    進入的時候可以用createdmounted鉤子,離開的時候用beforeDestorydestroyed鉤子,beforeDestory可以訪問thisdestroyed不可以訪問this

  • 緩存了組件:

緩存了組件之後,再次進入組件不會觸發`beforeCreate`、`created` 、`beforeMount`、 `mounted`,**如果你想每次進入組件都做一些事情的話,你可以放在`activated`進入緩存組件的鉤子中**。

同理:離開緩存組件的時候,`beforeDestroy`和`destroyed`並不會觸發,可以使用`deactivated`離開緩存組件的鉤子來代替。

觸發鉤子的完整順序:

將路由導航、keep-alive、和組件生命週期鉤子結合起來的,觸發順序,假設是從a組件離開,第一次進入b組件:

  1. beforeRouteLeave:路由組件的組件離開路由前鉤子,可取消路由離開。
  2. beforeEach: 路由全局前置守衛,可用於登錄驗證、全局路由loading等。
  3. beforeEnter: 路由獨享守衛
  4. beforeRouteEnter: 路由組件的組件進入路由前鉤子。
  5. beforeResolve:路由全局解析守衛
  6. afterEach:路由全局後置鉤子
  7. beforeCreate:組件生命週期,不能訪問this
  8. created:組件生命週期,可以訪問this,不能訪問dom。
  9. beforeMount:組件生命週期
  10. deactivated: 離開緩存組件a,或者觸發a的beforeDestroydestroyed組件銷燬鉤子。
  11. mounted:訪問/操作dom。
  12. activated:進入緩存組件,進入a的嵌套子組件(如果有的話)。
  13. 執行beforeRouteEnter回調函數next。

結語

Vue提供了很多鉤子,但很多鉤子我們幾乎不會用到,只有清楚這些鉤子函數的觸發順序以及背後的一些限制等,這樣我們才能夠正確的使用這些鉤子,希望看了本文的同學,能對這些鉤子有更加清晰的認識,使用起來更加得心應手。

希望看完的朋友可以點個喜歡/關注,您的支持是對我最大的鼓勵。

如果喜歡本文的話,歡迎關注我的訂閱號,漫漫技術路,期待未來共同學習成長。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章