深入理解Vue 的鉤子函數(shù)
前言
說(shuō)到Vue的鉤子函數(shù),可能很多人只停留在一些很簡(jiǎn)單常用的鉤子( created , mounted ),而且對(duì)于里面的區(qū)別,什么時(shí)候該用什么鉤子,并沒(méi)有仔細(xì)的去研究過(guò),且Vue的生命周期在面試中也算是比較高頻的考點(diǎn),那么該如何回答這類(lèi)問(wèn)題,讓人有眼前一亮的感覺(jué)呢...
Vue-Router導(dǎo)航守衛(wèi)
有的時(shí)候,我們需要通過(guò)路由來(lái)進(jìn)行一些操作,比如最常見(jiàn)的登錄權(quán)限驗(yàn)證,當(dāng)用戶滿足條件時(shí),才讓其進(jìn)入導(dǎo)航,否則就取消跳轉(zhuǎn),并跳到登錄頁(yè)面讓其登錄。
為此我們有很多種方法可以植入路由的導(dǎo)航過(guò)程: 全局的, 單個(gè)路由獨(dú)享的, 或者組件級(jí)的 , 推薦優(yōu)先閱讀官方路由文檔
全局守衛(wèi)
vue-router全局有三個(gè)守衛(wèi):
- router.beforeEach 全局前置守衛(wèi) 進(jìn)入路由之前
- router.beforeResolve 全局解析守衛(wèi)(2.5.0+) 在beforeRouteEnter調(diào)用之后調(diào)用
- router.afterEach 全局后置鉤子 進(jìn)入路由之后
使用方法:
// 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 這三個(gè)參數(shù):
to和from是 將要進(jìn)入和將要離開(kāi)的路由對(duì)象 ,路由對(duì)象指的是平時(shí)通過(guò)this.$route獲取到的路由對(duì)象。
next:Function這個(gè)參數(shù)是個(gè)函數(shù),且 必須調(diào)用,否則不能進(jìn)入路由 (頁(yè)面空白)。
- next() 進(jìn)入該路由。
- next(false): 取消進(jìn)入路由,url地址重置為from路由地址(也就是將要離開(kāi)的路由地址)。
- next 跳轉(zhuǎn)新路由,當(dāng)前的導(dǎo)航被中斷,重新開(kāi)始一個(gè)新的導(dǎo)航。
我們可以這樣跳轉(zhuǎn):next('path地址')
或者 next({path:''})
或者 next({name:''})
且允許設(shè)置諸如 replace: true、name: 'home' 之類(lèi)的選項(xiàng) 以及你用在router-link或router.push的對(duì)象選項(xiàng)。
路由獨(dú)享守衛(wèi)
如果你不想全局配置守衛(wèi)的話,你可以為某些路由單獨(dú)配置守衛(wèi):
const router = new VueRouter({ routes : [ { path : '/foo' , component : Foo , beforeEnter : ( to , from , next ) => { // ... } } ] })
路由組件內(nèi)的守衛(wèi):
- beforeRouteEnter 進(jìn)入路由前
- beforeRouteUpdate (2.2) 路由復(fù)用同一個(gè)組件時(shí)
- beforeRouteLeave 離開(kāi)當(dāng)前路由時(shí)
文檔中的介紹:
beforeRouteEnter (to, from, next) { }, beforeRouteUpdate ( to , from , next ) { // 在當(dāng)前路由改變,但是該組件被復(fù)用時(shí)調(diào)用 可以訪問(wèn)組件實(shí)例 `this` }, beforeRouteLeave ( to , from , next ) { // 導(dǎo)航離開(kāi)該組件的對(duì)應(yīng)路由時(shí)調(diào)用,可以訪問(wèn)組件實(shí)例 `this` }
beforeRouteEnter訪問(wèn)this
因?yàn)殂^子在組件實(shí)例還沒(méi)被創(chuàng)建的時(shí)候調(diào)用,所以不能獲取組件實(shí)例 this ,可以通過(guò)傳一個(gè)回調(diào)給 next 來(lái)訪問(wèn)組件實(shí)例 。
但是 回調(diào)的執(zhí)行時(shí)機(jī)在mounted后面 ,所以在我看來(lái)這里對(duì)this的訪問(wèn)意義不太大,可以放在 created 或者 mounted 里面。
beforeRouteEnter (to, from, next) { console . log ( '在路由獨(dú)享守衛(wèi)后調(diào)用' ); next ( vm => { // 通過(guò) `vm` 訪問(wèn)組件實(shí)例`this` 執(zhí)行回調(diào)的時(shí)機(jī)在mounted后面, }) }
beforeRouteLeave:
導(dǎo)航離開(kāi)該組件的對(duì)應(yīng)路由時(shí)調(diào)用,我們用它來(lái)禁止用戶離開(kāi),比如還未保存草稿,或者在用戶離開(kāi)前,將 setInterval 銷(xiāo)毀,防止離開(kāi)之后,定時(shí)器還在調(diào)用。
beforeRouteLeave (to, from , next) { if ( 文章保存 ) { next (); // 允許離開(kāi)或者可以跳到別的路由 上面講過(guò)了 } else { next ( false ); // 取消離開(kāi) } }
關(guān)于鉤子的一些知識(shí):
路由鉤子函數(shù)的錯(cuò)誤捕獲
如果我們?cè)谌质匦l(wèi)/路由獨(dú)享守衛(wèi)/組件路由守衛(wèi)的鉤子函數(shù)中有錯(cuò)誤,可以這樣捕獲:
router.onError(callback => { // 2.4.0新增 并不常用,了解一下就可以了 console . log ( callback , 'callback' ); });
在路由文檔中還有更多的實(shí)例方法:動(dòng)態(tài)添加路由等,有興趣可以了解一下。
跳轉(zhuǎn)死循環(huán),頁(yè)面永遠(yuǎn)空白
我了解到的,很多人會(huì)碰到這個(gè)問(wèn)題,來(lái)看一下這段偽代碼:
router.beforeEach((to, from, next) => { if ( 登錄 ){ next () } else { next ({ name : 'login' }); } });
看邏輯貌似是對(duì)的,但是當(dāng)我們跳轉(zhuǎn)到 login 之后,因?yàn)榇藭r(shí)還是未登錄狀態(tài),所以會(huì)一直跳轉(zhuǎn)到 login 然后死循環(huán),頁(yè)面一直是空白的,所以:我們需要把判斷條件稍微改一下。
if(登錄 || to.name === 'login'){ next() } //
登錄,或者將要前往login頁(yè)面的時(shí)候,就允許進(jìn)入路由
全局后置鉤子的跳轉(zhuǎn):
文檔中提到因?yàn)閞outer.afterEach不接受 next 函數(shù)所以也不會(huì)改變導(dǎo)航本身,意思就是只能當(dāng)成一個(gè)鉤子來(lái)使用,但是我自己在試的時(shí)候發(fā)現(xiàn),我們可以通過(guò)這種形式來(lái)實(shí)現(xiàn)跳轉(zhuǎn):
// main.js 入口文件 import router from './router' ; // 引入路由 router . afterEach (( to , from ) => { if ( 未登錄 && to . name !== 'login' ) { router . push ({ name : 'login' }); // 跳轉(zhuǎn)login } });
額,通過(guò)router.beforeEach
也完全可以實(shí)現(xiàn)且更好,我就騷一下。
完整的路由導(dǎo)航解析流程(不包括其他生命周期):
- 觸發(fā)進(jìn)入其他路由。
- 調(diào)用要離開(kāi)路由的組件守衛(wèi) beforeRouteLeave
- 調(diào)用局前置守衛(wèi): beforeEach
- 在重用的組件里調(diào)用 beforeRouteUpdate
- 調(diào)用路由獨(dú)享守衛(wèi) beforeEnter 。
- 解析異步路由組件。
- 在將要進(jìn)入的路由組件中調(diào)用 beforeRouteEnter
- 調(diào)用全局解析守衛(wèi) beforeResolve
- 導(dǎo)航被確認(rèn)。
- 調(diào)用全局后置鉤子的 afterEach 鉤子。
- 觸發(fā)DOM更新( mounted )。
- 執(zhí)行 beforeRouteEnter 守衛(wèi)中傳給 next 的回調(diào)函數(shù)
你不知道的keep-alive[我猜你不知道]
在開(kāi)發(fā)Vue項(xiàng)目的時(shí)候,大部分組件是沒(méi)必要多次渲染的,所以Vue提供了一個(gè)內(nèi)置組件 keep-alive 來(lái) 緩存組件內(nèi)部狀態(tài),避免重新渲染 。
文檔:和 <transition> 相似, <keep-alive> 是一個(gè)抽象組件:它自身不會(huì)渲染一個(gè) DOM 元素,也不會(huì)出現(xiàn)在父組件鏈中。
用法:
緩存動(dòng)態(tài)組件:
<keep-alive> 包裹動(dòng)態(tài)組件時(shí),會(huì)緩存不活動(dòng)的組件實(shí)例,而不是銷(xiāo)毀它們,此種方式并無(wú)太大的實(shí)用意義。
<!-- 基本 --> < keep - alive > < component : is = "view" ></ component > </ keep - alive > <!-- 多個(gè)條件判斷的子組件 --> < keep - alive > < comp - a v - if = "a > 1" ></ comp - a > < comp - b v - else ></ comp - b > </ keep - alive >
緩存路由組件:
使用 keep-alive 可以將所有路徑匹配到的路由組件都緩存起來(lái),包括路由組件里面的組件, keep-alive 大多數(shù)使用場(chǎng)景就是這種。
<keep-alive> < router - view ></ router - view > </ keep - alive >
生命周期鉤子:
這篇既然是Vue鉤子函數(shù)的專場(chǎng),那肯定要扣題呀~
在被 keep-alive 包含的組件/路由中,會(huì)多出兩個(gè)生命周期的鉤子: activated 與 deactivated 。
文檔:在 2.2.0 及其更高版本中,activated 和 deactivated 將會(huì)在 樹(shù)內(nèi)的 所有嵌套組件 中觸發(fā)。
activated 在組件第一次渲染時(shí)會(huì)被調(diào)用,之后在每次緩存組件被激活時(shí)調(diào)用。
activated調(diào)用時(shí)機(jī):
第一次進(jìn)入緩存路由/組件,在 mounted 后面, beforeRouteEnter 守衛(wèi)傳給 next 的回調(diào)函數(shù)之前調(diào)用:
beforeMount=> 如果你是從別的路由/組件進(jìn)來(lái)(組件銷(xiāo)毀destroyed/或離開(kāi)緩存deactivated)=>
mounted=> activated 進(jìn)入緩存組件 => 執(zhí)行 beforeRouteEnter回調(diào)
因?yàn)榻M件被緩存了,再次進(jìn)入緩存路由/組件時(shí),不會(huì)觸發(fā)這些鉤子:
beforeCreate created beforeMount mounted 都不會(huì)觸發(fā)。
所以之后的調(diào)用時(shí)機(jī)是:
組件銷(xiāo)毀destroyed/或離開(kāi)緩存deactivated => activated 進(jìn)入當(dāng)前緩存組件
=> 執(zhí)行 beforeRouteEnter回調(diào)
組件緩存或銷(xiāo)毀,嵌套組件的銷(xiāo)毀和緩存也在這里觸發(fā)
deactivated:組件被停用(離開(kāi)路由)時(shí)調(diào)用
使用了keep-alive就不會(huì)調(diào)用beforeDestroy(組件銷(xiāo)毀前鉤子)和destroyed(組件銷(xiāo)毀),因?yàn)榻M件沒(méi)被銷(xiāo)毀,被緩存起來(lái)了。
這個(gè)鉤子可以看作 beforeDestroy 的替代,如果你緩存了組件,要在組件銷(xiāo)毀的的時(shí)候做一些事情,你可以放在這個(gè)鉤子里。
如果你離開(kāi)了路由,會(huì)依次觸發(fā):
組件內(nèi)的離開(kāi)當(dāng)前路由鉤子beforeRouteLeave => 路由前置守衛(wèi) beforeEach =>
全局后置鉤子afterEach => deactivated 離開(kāi)緩存組件 => activated 進(jìn)入緩存組件(如果你進(jìn)入的也等于是緩存路由)
如果離開(kāi)的組件沒(méi)有緩存的話 beforeDestroy會(huì)替換deactivated
如果進(jìn)入的路由也沒(méi)有緩存的話 全局后置鉤子afterEach=>銷(xiāo)毀 destroyed=> beforeCreate等
那么,如果我只是想緩存其中幾個(gè)路由/組件,那該怎么做?
緩存你想緩存的路由:
Vue2.1.0之前:
想實(shí)現(xiàn)類(lèi)似的操作,你可以:
- 配置一下路由元信息
- 創(chuàng)建兩個(gè) keep-alive 標(biāo)簽
- 使用 v-if 通過(guò)路由元信息判斷緩存哪些路由。
<keep-alive> < router - view v - if = "$route.meta.keepAlive" > <!-- 這里是會(huì)被緩存的路由 --> </ router - view > </ keep - alive > < router - view v - if = "!$route.meta.keepAlive" > <!-- 因?yàn)橛玫氖莢 - if 所以下面還要?jiǎng)?chuàng)建一個(gè)未緩存的路由視圖出口 --> </ 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版本之后:
使用路由元信息的方式,要多創(chuàng)建一個(gè) router-view 標(biāo)簽,并且每個(gè)路由都要配置一個(gè)元信息,是可以實(shí)現(xiàn)我們想要的效果,但是過(guò)于繁瑣了點(diǎn)。
幸運(yùn)的是在Vue2.1.0之后,Vue新增了兩個(gè)屬性配合 keep-alive 來(lái)有條件地緩存 路由/組件。
新增屬性:
- include :匹配的 路由/組件 會(huì)被緩存
- exclude :匹配的 路由/組件 不會(huì)被緩存
include 和 exclude 支持三種方式來(lái)有條件的緩存路由:采用逗號(hào)分隔的字符串形式,正則形式,數(shù)組形式。
正則和數(shù)組形式,必須采用 v-bind 形式來(lái)使用。
緩存組件的使用方式:
<!-- 逗號(hào)分隔字符串 --> < keep - alive include = "a,b" > < component : is = "view" ></ component > </ keep - alive > <!-- 正則表達(dá)式 ( 使用 `v-bind` ) --> < keep - alive : include = "/a|b/" > < component : is = "view" ></ component > </ keep - alive > <!-- 數(shù)組 ( 使用 `v-bind` ) --> < keep - alive : include = "['a', 'b']" > < component : is = "view" ></ component > </ keep - alive >
但更多場(chǎng)景中,我們會(huì)使用keep-alive來(lái)緩存路由:
<keep-alive include='a'> < router - view ></ router - view > </ keep - alive >
匹配規(guī)則:
- 首先匹配組件的name選項(xiàng),如果 name 選項(xiàng)不可用。
- 則匹配它的 局部注冊(cè)名稱 。 (父組件 components 選項(xiàng)的鍵值)
- 匿名組件,不可匹配。
比如路由組件沒(méi)有 name 選項(xiàng),并且沒(méi)有注冊(cè)的組件名。
只能匹配當(dāng)前被包裹的組件, 不能匹配更下面嵌套的子組件 。
比如用在路由上,只能匹配路由組件的 name 選項(xiàng),不能匹配路由組件里面的嵌套組件的 name 選項(xiàng)。
文檔: <keep-alive> 不會(huì)在函數(shù)式組件中正常工作 ,因?yàn)樗鼈儧](méi)有緩存實(shí)例。
exclude 的優(yōu)先級(jí)大于 include
也就是說(shuō):當(dāng) include 和 exclude 同時(shí)存在時(shí), exclude 生效, include 不生效。
<keep-alive include="a,b" exclude="a"> <!-- 只有a不被緩存 --> < router - view ></ router - view > </ keep - alive >
當(dāng)組件被exclude匹配,該組件將不會(huì)被緩存,不會(huì)調(diào)用activated 和 deactivated。
組件生命周期鉤子:
關(guān)于組件的生命周期,是時(shí)候放出這張圖片了:
這張圖片已經(jīng)講得很清楚了,很多人這部分也很清楚了,大部分生命周期并不會(huì)用到,這里提一下幾點(diǎn):
- ajax請(qǐng)求最好放在 created 里面 ,因?yàn)榇藭r(shí)已經(jīng)可以訪問(wèn) this 了,請(qǐng)求到數(shù)據(jù)就可以直接放在 data 里面。
- 這里也碰到過(guò)幾次,面試官問(wèn):ajax請(qǐng)求應(yīng)該放在哪個(gè)生命周期。
- 關(guān)于dom的操作要放在 mounted 里面 ,在 mounted 前面訪問(wèn)dom會(huì)是 undefined 。
- 每次進(jìn)入/離開(kāi)組件都要做一些事情,用什么鉤子:
不緩存:
進(jìn)入的時(shí)候可以用 created 和 mounted 鉤子,離開(kāi)的時(shí)候用 beforeDestory 和 destroyed 鉤子, beforeDestory 可以訪問(wèn) this , destroyed 不可以訪問(wèn) this 。
緩存了組件:
緩存了組件之后,再次進(jìn)入組件不會(huì)觸發(fā) beforeCreate 、 created 、 beforeMount 、 mounted , 如果你想每次進(jìn)入組件都做一些事情的話,你可以放在 activated 進(jìn)入緩存組件的鉤子中 。
同理:離開(kāi)緩存組件的時(shí)候, beforeDestroy 和 destroyed 并不會(huì)觸發(fā),可以使用 deactivated 離開(kāi)緩存組件的鉤子來(lái)代替。
觸發(fā)鉤子的完整順序:
將路由導(dǎo)航、 keep-alive 、和組件生命周期鉤子結(jié)合起來(lái)的,觸發(fā)順序,假設(shè)是從a組件離開(kāi),第一次進(jìn)入b組件:
- beforeRouteLeave :路由組件的組件離開(kāi)路由前鉤子,可取消路由離開(kāi)。
- beforeEach : 路由全局前置守衛(wèi),可用于登錄驗(yàn)證、全局路由loading等。
- beforeEnter : 路由獨(dú)享守衛(wèi)
- beforeRouteEnter : 路由組件的組件進(jìn)入路由前鉤子。
- beforeResolve : 路由全局解析守衛(wèi)
- afterEach :路由全局后置鉤子
- beforeCreate :組件生命周期,不能訪問(wèn) this 。
- created :組件生命周期,可以訪問(wèn) this ,不能訪問(wèn)dom。
- beforeMount :組件生命周期
- deactivated : 離開(kāi)緩存組件a,或者觸發(fā)a的 beforeDestroy 和 destroyed 組件銷(xiāo)毀鉤子。
- mounted :訪問(wèn)/操作dom。
- activated :進(jìn)入緩存組件,進(jìn)入a的嵌套子組件(如果有的話)。
- 執(zhí)行beforeRouteEnter回調(diào)函數(shù)next。
結(jié)語(yǔ)
Vue提供了很多鉤子,但很多鉤子我們幾乎不會(huì)用到,只有清楚這些鉤子函數(shù)的觸發(fā)順序以及背后的一些限制等,這樣我們才能夠正確的使用這些鉤子,希望看了本文的同學(xué),能對(duì)這些鉤子有更加清晰的認(rèn)識(shí),使用起來(lái)更加得心應(yīng)手。
總結(jié)
以上所述是小編給大家介紹的Vue 的鉤子函數(shù),希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
v-for中動(dòng)態(tài)校驗(yàn)el-form表單項(xiàng)的實(shí)踐
在項(xiàng)目開(kāi)發(fā)中,我們經(jīng)常會(huì)遇到表單保存的功能,本文主要介紹了v-for中動(dòng)態(tài)校驗(yàn)el-form表單項(xiàng)的實(shí)踐,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧<BR>2022-05-05vue2.0 父組件給子組件傳遞數(shù)據(jù)的方法
在父組件 App.vue 中引用子組件 A.vue,把 name 的值傳給 A 組件。這篇文章主要介紹了vue2.0 父組件給子組件傳遞數(shù)據(jù)的方法,需要的朋友可以參考下2018-01-01一文詳解vue-router中的導(dǎo)航守衛(wèi)
vue-router提供的導(dǎo)航守衛(wèi)主要用來(lái)通過(guò)跳轉(zhuǎn)或取消的方式守衛(wèi)導(dǎo)航,在 vue-router 中,導(dǎo)航守衛(wèi)是一種非常重要的功能,所以本文將詳細(xì)講解一下vue-router中的導(dǎo)航守衛(wèi),感興趣的同學(xué)跟著小編一起來(lái)看看吧2023-07-07解決vue2中使用axios http請(qǐng)求出現(xiàn)的問(wèn)題
下面小編就為大家分享一篇解決vue2中使用axios http請(qǐng)求出現(xiàn)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-03-03Vue渲染器如何對(duì)節(jié)點(diǎn)進(jìn)行掛載和更新
這篇文章主要介紹了Vue 的渲染器是如何對(duì)節(jié)點(diǎn)進(jìn)行掛載和更新的,文中通過(guò)代碼示例給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-05-05詳談vue+webpack解決css引用圖片打包后找不到資源文件的問(wèn)題
下面小編就為大家分享一篇詳談vue+webpack解決css引用圖片打包后找不到資源文件的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-03-03Vue 框架之動(dòng)態(tài)綁定 css 樣式實(shí)例分析
這篇文章主要介紹了Vue 框架之動(dòng)態(tài)綁定 css 樣式的方法,本文通過(guò)分享小實(shí)例給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-11-11Vue實(shí)現(xiàn)雙向綁定的原理以及響應(yīng)式數(shù)據(jù)的方法
這篇文章主要介紹了Vue實(shí)現(xiàn)雙向綁定的原理以及響應(yīng)式數(shù)據(jù)的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07