Vue父子組件生命週期執行順序及鉤子函數的我的理解

先附一張官網上的vue實例的生命週期圖,每一個Vue實例在被建立的時候都須要通過一系列的初始化過程,例如須要設置數據監聽,編譯模板,將實例掛載到DOM並在數據變化時更新DOM等。同時在這個過程當中也會運行一些叫作生命週期鉤子的函數(回調函數),這給了用戶在不一樣階段添加本身代碼的機會。html

一、vue的生命週期圖

 

在vue實例的整個生命週期的各個階段,會提供不一樣的鉤子函數以供咱們進行不一樣的操做。先列出vue官網上對各個鉤子函數的詳細解析。vue

生命週期鉤子    node

詳細
beforeCreate 在實例初始化以後,數據觀測(data observer) 和 event/watcher 事件配置以前被調用。
created

實例已經建立完成以後被調用。在這一步,實例已完成如下的配置:數據觀測(data observer),屬性和方法的運算, watch/event 事件回調。ajax

在執行data()方法前props屬性有數據已經能夠訪問,watch和computed監聽函數此時爲null,此時this.computed裏的計算屬性值爲undefined。data函數執行完後,watch和computed監聽函數纔可用,由於data函數執行完後,data函數return的屬性這時纔可用。然而,掛載階段還沒開始,$el 屬性目前不可見。vue-router

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 實例指示的全部東西都會解綁定,全部的事件監聽器會被移除,全部的子實例也會被銷燬。

二、實際操做 

下面咱們在實際的代碼執行過程當中理解父子組件生命週期建立過程以及鉤子函數執行的實時狀態變化。segmentfault

測試基於下面的代碼,引入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.一、生命週期調試

首先咱們建立了一個Vue實例vm,將其掛載到頁面中id爲「app」的元素上。markdown

3.1.一、根組件的beforeCreate階段app

能夠看出,在調用beforeCreate()函數時,只進行了一些必要的初始化操做(例如一些全局的配置和根實例的一些屬性初始化),此時data屬性爲undefined,沒有可供操做的數據。dom

3.1.二、根組件的Created階段

調用Created()函數,在這一步,實例已完成如下的配置:數據代理和動態數據綁定(data observer),屬性和方法的運算, watch/event 事件回調。然而,掛載階段還沒開始,$el 屬性目前不可見。

3.1.三、根組件的beforeMount階段

在調用boforeMount()函數前首先會判斷對象是否有el選項。若是有的話就繼續向下編譯,若是沒有el選項,則中止編譯,也就意味着中止了生命週期,直到在該vue實例上調用vm.$mount(el)

在這個例子中,咱們有el元素,所以會調用boforeMount()函數,此時已經開始執行模板解析函數,但尚未將$el元素掛載頁面,頁面視圖所以也未更新。在標紅處,仍是 {{message}},這裏就是應用的 Virtual DOM(虛擬Dom)技術,先把坑佔住了。到後面mounted掛載的時候再把值渲染進去。

3.1.四、子組件的beforeCreate、Created、beforeMount、Mounted階段

在父組件執行beforeMount階段後,進入子組件的beforeCreate、Created、beforeMount階段,這些階段和父組件相似,按下不表。beforeMount階段後,執行的是Mounted階段,該階段時子組件已經掛載到父組件上,而且父組件隨之掛載到頁面中。

由下圖能夠知道,在beforeMount階段以後、Mounted階段以前,數據已經被加載到視圖上了,即$el元素被掛載到頁面時觸發了視圖的更新

3.1.五、子組件的activated階段

 咱們發如今子父組件所有掛載到頁面以後被觸發。這是由於子組件my-components被<keep-alive> 包裹,隨$el的掛載被觸發。若是子組件沒有被<keep-alive>包裹,那麼該階段將不會被觸發。

3.1.六、父組件的mounted階段

mounted執行時:此時el已經渲染完成並掛載到實例上。

至此,從Vue實例的初始化到將新的模板掛載到頁面上的階段已經完成,退出debugger。下面咱們來看一下deactivated、beforeUpdate、updated、beforeDestroy、destroyed鉤子函數。

3.二、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.三、beforeDestroy和destroyed鉤子函數間的生命週期

如今咱們對Vue實例進行銷燬,調用app.$destroy()方法便可將其銷燬,控制檯測試以下:

 

咱們發現實例依然存在,可是此時變化已經發生在了其餘地方。

beforeDestroy鉤子函數在實例銷燬以前調用。在這一步,實例仍然徹底可用。

destroyed鉤子函數在Vue 實例銷燬後調用。調用後,Vue 實例指示的全部東西都會解綁定,全部的事件監聽器會被移除,全部的子實例也會被銷燬(也就是說子組件也會觸發相應的函數)。這裏的銷燬並不指代'抹去',而是表示'解綁'。

銷燬時beforeDestory函數的傳遞順序爲由父到子,destory的傳遞順序爲由子到父。

四、一些應用鉤子函數的想法

  • 在created鉤子中能夠對data數據進行操做,這個時候能夠進行ajax請求將返回的數據賦給data。
  • 雖然updated函數會在數據變化時被觸發,但卻不能準確的判斷是那個屬性值被改變,因此在實際狀況中用computed或match函數來監聽屬性的變化,並作一些其餘的操做。
  • 在mounted鉤子對掛載的dom進行操做,此時,DOM已經被渲染到頁面上。
  • 在使用vue-router時有時須要使用<keep-alive></keep-alive>來緩存組件狀態,這個時候created鉤子就不會被重複調用了,若是咱們的子組件須要在每次加載或切換狀態的時候進行某些操做,可使用activated鉤子觸發。
  • 全部的生命週期鉤子自動綁定 this 上下文到實例中,因此不能使用箭頭函數來定義一個生命週期方法 (例如 created: () => this.fetchTodos())。這是致使this指向父級。

五、 小結

  • 加載渲染過程

  父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

  • 子組件更新過程

  父beforeUpdate->子beforeUpdate->子updated->父updated

  • 父組件更新過程

  父beforeUpdate->父updated

  • 銷燬過程

  父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

六、參考文章

關於Vue.js2.0生命週期的研究與理解

Vue2.0 探索之路——生命週期和鉤子函數的一些理解

 

PS:若是文章對您有一些幫助,歡迎點擊推薦,您的推薦是我不斷輸出的動力!

相關文章
相關標籤/搜索