VUE生命周期中的鉤子函數及父子組件的執行順序
先附一張官網上的vue實例的生命周期圖,每個Vue實例在被創建的時候都需要經過一系列的初始化過程,例如需要設置數據監聽,編譯模板,將實例掛載到DOM並在數據變化時更新DOM等。同時在這個過程中也會運行一些叫做生命周期鉤子的函數(回調函數),這給了用戶在不同階段添加自己代碼的機會。
1、vue的生命周期圖
在vue實例的整個生命周期的各個階段,會提供不同的鉤子函數以供我們進行不同的操作。先列出vue官網上對各個鉤子函數的詳細解析。
生命周期鉤子 | 詳細 |
---|---|
beforeCreate | 在實例初始化之後,數據觀測(data observer) 和 event/watcher 事件配置之前被調用。 |
created | 實例已經創建完成之後被調用。在這一步,實例已完成以下的配置:數據觀測(data observer),屬性和方法的運算, watch/event 事件回調。然而,掛載階段還沒開始,$el 屬性目前不可見。 |
beforeMount | 在掛載開始之前被調用:相關的 render 函數首次被調用。 |
mounted | el 被新創建的 vm.$el 替換,並掛載到實例上去之後調用該鉤子。如果 root 實例掛載了一個文檔內元素,當 mounted 被調用時 vm.$el 也在文檔內。 |
beforeUpdate | 數據更新時調用,發生在虛擬 DOM 重新渲染和打補丁之前。你可以在這個鉤子中進一步地更改狀態,這不會觸發附加的重渲染過程。 |
updated | 由於數據更改導致的虛擬 DOM 重新渲染和打補丁,在這之後會調用該鉤子。當這個鉤子被調用時,組件 DOM 已經更新,所以你現在可以執行依賴於 DOM 的操作。 |
activated | keep-alive 組件激活時調用。 |
deactivated | keep-alive 組件停用時調用。 |
beforeDestroy | 實例銷毀之前調用。在這一步,實例仍然完全可用。 |
destroyed | Vue 實例銷毀後調用。調用後,Vue 實例指示的所有東西都會解綁定,所有的事件監聽器會被移除,所有的子實例也會被銷毀。 |
2、實際操作
下面我們在實際的代碼執行過程中理解父子組件生命周期創建過程以及鉤子函數執行的實時狀態變化。
測試基於下面的代碼,引入vue.js文件後即可執行。(打開頁面後,再按一次刷新會自動進入debugger狀態)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style> </style> </head> <body> <div id="app"> <p>{{message}}</p> <keep-alive> <my-components :msg="msg1" v-if="show"></my-components> </keep-alive> </div> </body> <script src="../../node_modules/vue/dist/vue.js"></script> <script> var child = { template: ‘<div>from child: {{childMsg}}</div>‘, props: [‘msg‘], data: function() { return { childMsg: ‘child‘ } }, beforeCreate: function () { debugger; }, created: function () { debugger; }, beforeMount: function () { debugger; }, mounted: function () { debugger; }, deactivated: function(){ alert("keepAlive停用"); }, activated: function () { console.log(‘component activated‘); }, beforeDestroy: function () { console.group(‘beforeDestroy 銷毀前狀態===============》‘); var state = { ‘el‘: this.$el, ‘data‘: this.$data, ‘message‘: this.message } console.log(this.$el); console.log(state); }, destroyed: function () { console.group(‘destroyed 銷毀完成狀態===============》‘); var state = { ‘el‘: this.$el, ‘data‘: this.$data, ‘message‘: this.message } console.log(this.$el); console.log(state); }, }; var vm = new Vue({ el: ‘#app‘, data: { message: ‘father‘, msg1: "hello", show: true }, beforeCreate: function () { debugger; }, created: function () { debugger; }, beforeMount: function () { debugger; }, mounted: function () { debugger; }, beforeUpdate: function () { alert("頁面視圖更新前"); }, updated: function () { alert("頁面視圖更新後"); }, beforeDestroy: function () { console.group(‘beforeDestroy 銷毀前狀態===============》‘); var state = { ‘el‘: this.$el, ‘data‘: this.$data, ‘message‘: this.message } console.log(this.$el); console.log(state); }, destroyed: function () { console.group(‘destroyed 銷毀完成狀態===============》‘); var state = { ‘el‘: this.$el, ‘data‘: this.$data, ‘message‘: this.message } console.log(this.$el); console.log(state); }, components: { ‘my-components‘: child } }); </script> </html>
3.1、生命周期調試
首先我們創建了一個Vue實例vm,將其掛載到頁面中id為“app”的元素上。
3.1.1、根組件的beforeCreate階段
可以看出,在調用beforeCreate()函數時,只進行了一些必要的初始化操作(例如一些全局的配置和根實例的一些屬性初始化),此時data屬性為undefined,沒有可供操作的數據。
3.1.2、根組件的Created階段
調用Created()函數,在這一步,實例已完成以下的配置:數據代理和動態數據綁定(data observer),屬性和方法的運算, watch/event 事件回調。然而,掛載階段還沒開始,$el 屬性目前不可見。
3.1.3、根組件的beforeMount階段
在調用boforeMount()函數前首先會判斷對象是否有el選項。如果有的話就繼續向下編譯,如果沒有el選項,則停止編譯,也就意味著停止了生命周期,直到在該vue實例上調用vm.$mount(el)
在這個例子中,我們有el元素,因此會調用boforeMount()函數,此時已經開始執行模板解析函數,但還沒有將$el元素掛載頁面,頁面視圖因此也未更新。在標紅處,還是 {{message}},這裏就是應用的 Virtual DOM
(虛擬Dom)技術,先把坑占住了。到後面mounted
掛載的時候再把值渲染進去。
3.1.4、子組件的beforeCreate、Created、beforeMount、Mounted階段
在父組件執行beforeMount階段後,進入子組件的beforeCreate、Created、beforeMount階段,這些階段和父組件類似,按下不表。beforeMount階段後,執行的是Mounted階段,該階段時子組件已經掛載到父組件上,並且父組件隨之掛載到頁面中。
由下圖可以知道,在beforeMount階段之後、Mounted階段之前,數據已經被加載到視圖上了,即$el元素被掛載到頁面時觸發了視圖的更新。
3.1.5、子組件的activated階段
我們發現在子父組件全部掛載到頁面之後被觸發。這是因為子組件my-components被<keep-alive> 包裹,隨$el的掛載被觸發。如果子組件沒有被<keep-alive>包裹,那麽該階段將不會被觸發。
3.1.6、父組件的mounted階段
mounted執行時:此時el已經渲染完成並掛載到實例上。
至此,從Vue實例的初始化到將新的模板掛載到頁面上的階段已經完成,退出debugger。下面我們來看一下deactivated、beforeUpdate、updated、beforeDestroy、destroyed鉤子函數。
3.2、deactivated、beforeUpdate、updated階段
由生命周期函數可知:當數據變化後、虛擬DOM渲染重新渲染頁面前會觸發beforeUpdate()函數,此時視圖還未改變。當虛擬DOM渲染頁面視圖更新後會觸發updated()函數。
我們不妨改變vm.show = false,當修改這個屬性時,不僅會觸發beforeUpdate、updated函數,還會觸發deactivated函數(因為keep-alive 組件停用時調用)。我們不妨想一下deactivated函數會在beforeUpdate後還是updated後調用。
我們在控制臺輸入vm.show = false。得到三者的調用順序分別為beforeUpdate、deactivated、updated。我們可以知道的是deactivated函數的觸發時間是在視圖更新時觸發。因為當視圖更新時才能知道keep-alive組件被停用了。
3.3、beforeDestroy和destroyed鉤子函數間的生命周期
現在我們對Vue實例進行銷毀,調用app.$destroy()方法即可將其銷毀,控制臺測試如下:
我們發現實例依然存在,但是此時變化已經發生在了其他地方。
beforeDestroy鉤子函數在實例銷毀之前調用。在這一步,實例仍然完全可用。
destroyed鉤子函數在Vue 實例銷毀後調用。調用後,Vue 實例指示的所有東西都會解綁定,所有的事件監聽器會被移除,所有的子實例也會被銷毀(也就是說子組件也會觸發相應的函數)。這裏的銷毀並不指代‘抹去‘,而是表示‘解綁‘。
銷毀時beforeDestory函數的傳遞順序為由父到子,destory的傳遞順序為由子到父。
4、一些應用鉤子函數的想法
- 在created鉤子中可以對data數據進行操作,這個時候可以進行ajax請求將返回的數據賦給data。
- 雖然updated函數會在數據變化時被觸發,但卻不能準確的判斷是那個屬性值被改變,所以在實際情況中用computed或match函數來監聽屬性的變化,並做一些其他的操作。
- 在mounted鉤子對掛載的dom進行操作,此時,DOM已經被渲染到頁面上。
- 在使用vue-router時有時需要使用<keep-alive></keep-alive>來緩存組件狀態,這個時候created鉤子就不會被重復調用了,如果我們的子組件需要在每次加載或切換狀態的時候進行某些操作,可以使用activated鉤子觸發。
- 所有的生命周期鉤子自動綁定
this
上下文到實例中,所以不能使用箭頭函數來定義一個生命周期方法 (例如created: () => this.fetchTodos()
)。這是導致this指向父級。
5、 小結
-
加載渲染過程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
- 子組件更新過程
父beforeUpdate->子beforeUpdate->子updated->父updated
- 父組件更新過程
父beforeUpdate->父updated
- 銷毀過程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
6、參考文章
關於Vue.js2.0生命周期的研究與理解
Vue2.0 探索之路——生命周期和鉤子函數的一些理解
Vue2.0 探索之路——vuex入門教程和思考
Vue2.0 探索之路——vue-router入門教程和總結
官方文檔:https://vuex.vuejs.org/zh/guide/
VUE生命周期中的鉤子函數及父子組件的執行順序