前言
說到Vue的鉤子函式,可能很多人只停留在一些很簡單常用的鉤子(created
,mounted
),而且對於裡面的區別,什麼時候該用什麼鉤子,並沒有仔細的去研究過,且Vue的生命週期在面試中也算是比較高頻的考點,那麼該如何回答這類問題,讓人有眼前一亮的感覺呢...
Vue-Router導航守衛:
有的時候,我們需要通過路由來進行一些操作,比如最常見的登入許可權驗證,當使用者滿足條件時,才讓其進入導航,否則就取消跳轉,並跳到登入頁面讓其登入。
為此我們有很多種方法可以植入路由的導航過程:全域性的, 單個路由獨享的, 或者元件級的,推薦優先閱讀路由文件
全域性守衛
vue-router全域性有三個守衛:
- router.beforeEach 全域性前置守衛 進入路由之前
- router.beforeResolve 全域性解析守衛(2.5.0+) 在beforeRouteEnter呼叫之後呼叫
- 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) => {
// 引數用法什麼的都一樣,呼叫順序在全域性前置守衛後面,所以不會被全域性守衛覆蓋
// ...
}
}
]
})
複製程式碼
路由元件內的守衛:
- beforeRouteEnter 進入路由前
- beforeRouteUpdate (2.2) 路由複用同一個元件時
- 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 也完全可以實現且更好,我就騷一下。
完整的路由導航解析流程(不包括其他生命週期):
- 觸發進入其他路由。
- 呼叫要離開路由的元件守衛
beforeRouteLeave
- 呼叫局前置守衛:
beforeEach
- 在重用的元件裡呼叫
beforeRouteUpdate
- 呼叫路由獨享守衛
beforeEnter
。 - 解析非同步路由元件。
- 在將要進入的路由元件中呼叫
beforeRouteEnter
- 呼叫全域性解析守衛
beforeResolve
- 導航被確認。
- 呼叫全域性後置鉤子的
afterEach
鉤子。 - 觸發DOM更新(
mounted
)。 - 執行
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之前:
想實現類似的操作,你可以:
-
配置一下路由元資訊
-
建立兩個
keep-alive
標籤 -
使用
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
:匹配的 路由/元件 不會被快取
include
和exclude
支援三種方式來有條件的快取路由:採用逗號分隔的字串形式,正則形式,陣列形式。
正則和陣列形式,必須採用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>
複製程式碼
匹配規則:
- 首先匹配元件的name選項,如果
name
選項不可用。 - 則匹配它的區域性註冊名稱。 (父元件
components
選項的鍵值) - 匿名元件,不可匹配。
比如路由元件沒有name
選項,並且沒有註冊的元件名。
- 只能匹配當前被包裹的元件,不能匹配更下面巢狀的子元件。
比如用在路由上,只能匹配路由元件的name
選項,不能匹配路由元件裡面的巢狀元件的name
選項。
- 文件:
<keep-alive>
不會在函式式元件中正常工作,因為它們沒有快取例項。 exclude
的優先順序大於include
也就是說:當include
和exclude
同時存在時,exclude
生效,include
不生效。
<keep-alive include="a,b" exclude="a">
<!--只有a不被快取-->
<router-view></router-view>
</keep-alive>
複製程式碼
當元件被exclude
匹配,該元件將不會被快取,不會呼叫activated
和 deactivated
。
元件生命週期鉤子:
關於元件的生命週期,是時候放出這張圖片了:
這張圖片已經講得很清楚了,很多人這部分也很清楚了,大部分生命週期並不會用到,這裡提一下幾點:
-
ajax請求最好放在
created
裡面,因為此時已經可以訪問this
了,請求到資料就可以直接放在data
裡面。這裡也碰到過幾次,面試官問:ajax請求應該放在哪個生命週期。
-
關於dom的操作要放在
mounted
裡面,在mounted
前面訪問dom會是undefined
。 -
每次進入/離開元件都要做一些事情,用什麼鉤子:
-
不快取:
進入的時候可以用
created
和mounted
鉤子,離開的時候用beforeDestory
和destroyed
鉤子,beforeDestory
可以訪問this
,destroyed
不可以訪問this
。 -
快取了元件:
快取了元件之後,再次進入元件不會觸發
beforeCreate
、created
、beforeMount
、mounted
,如果你想每次進入元件都做一些事情的話,你可以放在activated
進入快取元件的鉤子中。同理:離開快取元件的時候,
beforeDestroy
和destroyed
並不會觸發,可以使用deactivated
離開快取元件的鉤子來代替。
觸發鉤子的完整順序:
將路由導航、keep-alive
、和元件生命週期鉤子結合起來的,觸發順序,假設是從a元件離開,第一次進入b元件:
beforeRouteLeave
:路由元件的元件離開路由前鉤子,可取消路由離開。beforeEach
: 路由全域性前置守衛,可用於登入驗證、全域性路由loading等。beforeEnter
: 路由獨享守衛beforeRouteEnter
: 路由元件的元件進入路由前鉤子。beforeResolve
:路由全域性解析守衛afterEach
:路由全域性後置鉤子beforeCreate
:元件生命週期,不能訪問this
。created
:元件生命週期,可以訪問this
,不能訪問dom。beforeMount
:元件生命週期deactivated
: 離開快取元件a,或者觸發a的beforeDestroy
和destroyed
元件銷燬鉤子。mounted
:訪問/操作dom。activated
:進入快取元件,進入a的巢狀子元件(如果有的話)。- 執行beforeRouteEnter回撥函式next。
結語
Vue提供了很多鉤子,但很多鉤子我們幾乎不會用到,只有清楚這些鉤子函式的觸發順序以及背後的一些限制等,這樣我們才能夠正確的使用這些鉤子,希望看了本文的同學,能對這些鉤子有更加清晰的認識,使用起來更加得心應手。
希望看完的朋友可以點個喜歡/關注,您的支援是對我最大的鼓勵。
前端進階積累、公眾號、GitHub、wx:OBkoro1、郵箱:obkoro1@foxmail.com
以上2018.7.21
參考資料:
Vue文件