Vue2.0生命週期(元件鉤子函式與路由守衛)
元件相關鉤子函式: beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destoryed
還有兩個特殊的(使用keep-alive):activated、deactivated(不詳述)
v2.5.0+新增: errorCaptured (暫時還不知道咋用)
路由守衛:
全域性&路由獨享:beforeEach、beforeResolve(v2.5.0+新增)、afterEach ;beforeEnter(路由獨享,類似beforeEach)
元件內:beforeRouteEnter、beforeRouteUpdate (2.2 新增)、beforeRouteLeave
元件生命週期鉤子函式
beforeCreate
例項初始化之後
this指向建立的例項
資料觀測(data observer)和event/watcher配置尚未完成
不能訪問到methods、data、computed、watch上的方法和資料
created
例項建立完成,並已經完成以下配置:資料觀測(data observer),屬性和方法的運算, watch/event 事件回撥
此時可以呼叫methods中定義的方法,修改data的資料,並且可觸發響應式變化、computed值重新計算,watch到變更等
還未掛載到DOM,不能訪問到$el屬性,$ref屬性內容為空陣列
new Vue({ data () { return { a : 1 } } , created (){ console.log( this.a ) // 1 } })
這個生命週期階段比較常用,比如ajax請求獲取初始化資料對例項進行初始化預處理等操作;但要注意在結合vue-router使用時,進入created生命週期階段後是無法對掛載例項進行攔截的,此時例項已經建立完成;故如果需要某些資料獲取完成情況才允許進入頁面的場景,建議在路由鉤子beforeRouteEnter中實現
beforeMount
在掛載開始之前被呼叫
注意:從vue生命週期圖可以看出
當new Vue({...})的配置中沒有el屬性時,生命週期暫停,等待vm.$mount(el)呼叫時才繼續
beforeMount之前,會找到對應的template,並編譯成render函式
(這個步驟如果使用.vue檔案和執行時版本將會在構建時提前完成)
template查詢的優先順序順序:
template引數 > el 外部HTML
如果指定了render函式,則直接採用render函式,即忽略template引數和el外部HTML
寫個栗子測試:
<div id="app">template outside</div>
...
import App from './App.vue'; // App是任一Vue元件物件
new Vue({
el: '#app',
// template: '<p>template inside</p>', // part inside
// render: h => h(App) // part render
})
需要Vue完整版本支援,註釋part inside和part render依次開啟即可觀察到三次不同的結果
Vue的編譯過程暫略
mounted
el被新建立的$el替換 ---- 怎麼理解?
這個可以理解為掛載前為例項尋找了一個暫時容身之處el,編譯完成($el建立完成)後替換這個容身之處完成例項的掛載
如:之前那個栗子中,將part render開啟後觀察生成的DOM結構,<div id="app">template outside</div>
這個節點即原el已經被替換掉
例項掛載到DOM上,此時可以通過DOM API獲取到DOM節點,$ref屬性可以訪問
雖然經常觀察到先進入子元件mounted,但根據Vue官方文件:
注意 `mounted` 不會承諾所有的子元件也都一起被掛載。如果你希望等到整個檢視都渲染
完畢,可以用 [vm.$nextTick](https://cn.vuejs.org/v2/api/#vm-nextTick)
在這個階段改變data上的值,相關的computed屬性可以立刻更新;但需要進入到下一次DOM更新,才能看到DOM上資料更新
栗子:
new Vue({
el: '#app',
template: '<p id="testa">{{a}}</p>',
router,
data ()
{
return {
a : 0
}
},
mounted() {
this.a ++;
console.log(this.b); // 2
console.log(document.getElementById('testa').innerHTML) // 0
},
computed :{
b (){
return this.a+1;
}
}
})
beforeRouteEnter的next的勾子比mounted觸發還要靠後 - 這個待會說到路由相關鉤子時再展開
beforeUpdate
這裡的更新物件是模板,即需要虛擬 DOM 重新渲染和打補丁,beforeUpdate發生在以上兩個流程之前,此時新的虛擬DOM已經生成
如果發生變更的資料在模板中並沒有使用(包括直接和間接,間接:比如某個依賴該資料的計算屬性在模板中使用了),則不會觸發更新流程!!!
如:
new Vue({
el: '#app',
template: '<p id="testa">{{a}}</p>',
router,
data ()
{
return {
a : 0,
b : 1
}
},
mounted (){
this.b ++; // b並沒有在模板中使用
},
beforeUpdate (){
debugger; // 不會進入這個斷點
}
})
在一些參考文章中看到:在這個鉤子函式中,可以對狀態進行進一步更改,不會再次觸發重渲染流程
--- 這個說法有問題
如果對狀態進行變更會導致重新進入beforeUpdate(這裡變更的狀態同樣要在模板中使用,如果變更沒有在模板中使用的data,才不會再次觸發重渲染流程)
而且若變更操作不是基礎型別的簡單賦值,還會引起死迴圈(不斷重新進入beforeUpdate)!
看看這個栗子,依次把註釋開啟測試
new Vue({
el: '#app',
template: '<p id="testa">{{a}}</p>',
router,
data ()
{
return {
a : 0,
c: 0
}
},
beforeUpdate() {
console.log(document.getElementById('testa').innerHTML)
// this.c = 1; // this.c沒有在模板中使用,變更不會引起重渲染流程
// this.a = 3; // 會再次進入一次重渲染流程,第二次進入時發現a仍是3,值沒有變更,不會再次重渲染
// this.a ++; // 會引起死迴圈,每次進入重渲染流程時,a的值都會變更
},
updated() {
console.log(document.getElementById('testa').innerHTML)
}
})
應該避免在這個鉤子函式中操作資料
updated
由於資料更改導致的虛擬 DOM 重新渲染和打補丁,在這之後會呼叫該鉤子。
當這個鉤子被呼叫時,元件 DOM 已經更新,可以執行依賴於 DOM 的操作
注意 `updated` 不會承諾所有的子元件也都一起被重繪。如果你希望等到整個檢視都重繪完畢,
可以用 [vm.$nextTick](https://cn.vuejs.org/v2/api/#vm-nextTick)
同樣,應該避免在這個鉤子函式中操作資料
beforeDestroy
例項銷燬之前呼叫。在這一步,例項仍然完全可用,this仍能獲取到例項
一般在這一步中進行:銷燬定時器、解綁全域性事件、銷燬外掛物件等操作
destroyed
Vue 例項銷燬後呼叫。呼叫後,Vue 例項指示的所有東西都會解繫結,所有的事件監聽器會被移除,所有的子例項也會被銷燬
注意:vue2.0之後主動呼叫$destroy()不會移除dom節點,作者不推薦直接destroy這種做法,具體參考https://github.com/vuejs/vue/...,如果實在需要這樣用可以在這個生命週期鉤子中手動移除dom節點
總結
路由守衛 —— 路由級別的(全域性&路由獨享)
router.beforeEach
全域性前置守衛
當一個導航觸發時,全域性前置守衛按照建立順序呼叫。守衛是非同步解析執行,此時導航在所有守衛 resolve 完之前一直處於 等待中
如何使用:
router.beforeEach((to, from, next) => {
console.log('全域性前置守衛:beforeEach -- next需要呼叫')
next()
})
一般在這個守衛方法中進行全域性攔截,比如必須滿足某種條件(使用者登入等)才能進入路由的情況
引數to和from都是路由物件Route
next是個Function,有以下幾種用法(from api文件)
- next(): 進行管道中的下一個鉤子。如果全部鉤子執行完了,則導航的狀態就是 confirmed (確認的)
- next(false): 中斷當前的導航。如果瀏覽器的 URL 改變了(可能是使用者手動或者瀏覽器後退按鈕),那麼 URL 地址會重置到 from 路由對應的地址 —— 也就是說並不是單純的中斷,還會檢查URL的變更以保證不會錯誤的進入到next路由
- next('/') 或者 next({ path: '/' }): 跳轉到一個不同的地址。當前的導航被中斷,然後進行一個新的導航。可傳遞的引數與router.push中選項一致
- next(error): (v2.4.0+) 如果傳入
next
的引數是一個Error
例項,則導航會被終止且該錯誤會被傳遞給router.onError()
註冊過的回撥
router.beforeResolve (v 2.5.0+)
全域性解析守衛
和beforeEach類似,區別是在導航被確認之前,同時在所有元件內守衛和非同步路由元件被解析之後,解析守衛就被呼叫
即在 beforeEach 和 元件內beforeRouteEnter 之後
引數和beforeEach一致,也需要呼叫next對導航確認
router.afterEach
全域性後置鉤子
在所有路由跳轉結束的時候呼叫
這些鉤子不會接受 next 函式也不會改變導航本身
beforeEnter
可直接定義在路由配置上,和beforeEach方法引數、用法相同
路由守衛 —— 元件內
beforeRouteEnter
在渲染該元件的對應路由被確認
前呼叫,用法和引數與beforeEach類似,next需要被主動呼叫
注意:
- 此時元件例項還未被建立,不能訪問this
- 可以通過傳一個回撥給 next來訪問元件例項。在導航被確認的時候執行回撥,並且把元件例項作為回撥方法的引數
beforeRouteEnter (to, from, next) {
// 這裡還無法訪問到元件例項,this === undefined
next( vm => {
// 通過 `vm` 訪問元件例項
})
}
- 可以在這個守衛中請求服務端獲取資料,當成功獲取並能進入路由時,呼叫next並在回撥中通過
vm
訪問元件例項進行賦值等操作 - beforeRouteEnter觸發在導航確認、元件例項建立之前:beforeCreate之前;而next中函式的呼叫在mounted之後:為了確保能對元件例項的完整訪問
beforeRouteUpdate (v 2.2+)
在當前路由改變,並且該元件被複用時呼叫,可以通過this訪問例項, next需要被主動呼叫,不能傳回調
- 對於一個帶有動態引數的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候,元件例項會被複用,該守衛會被呼叫
- 當前路由query變更時,該守衛會被呼叫
- vue-router推薦的資料獲取方法二中,結合beforeRouteEnter使用,在路由引數變更時可以重新獲取資料,獲取成功再呼叫next(),參考:https://router.vuejs.org/zh-c...
之前在手機瀏覽器中好像發現這個守衛的bug?@TODO 待確認
beforeRouteLeave
導航離開該元件的對應路由時呼叫,可以訪問元件例項 this
,next需要被主動呼叫,不能傳回調
總結
結合並擴充套件Vue-router官方文件的說明:
- 導航行為被觸發,此時導航未被確認。
- 在失活的元件裡呼叫離開守衛 beforeRouteLeave。
- 呼叫全域性的 beforeEach 守衛。
- 在重用的元件裡呼叫 beforeRouteUpdate 守衛 (2.2+)。
- 在路由配置裡呼叫 beforeEnter。
- 解析非同步路由元件(如果有)。
- 在被啟用的元件裡呼叫 beforeRouteEnter。
- 呼叫全域性的 beforeResolve 守衛 (2.5+),標示解析階段完成。
- 導航被確認。
- 呼叫全域性的 afterEach 鉤子。
-
非重用元件,開始元件例項的生命週期
- beforeCreate&created
- beforeMount&mounted
- 觸發 DOM 更新。
- 用建立好的例項呼叫 beforeRouteEnter 守衛中傳給 next 的回撥函式。
- 導航完成