# Vue 實體的生命週期

一個 Vue 實體有生老病死,而 Vue 實體會在各個生命階段提供鉤子事件( Hook Event )讓開發者可以在實體的不同階段做想要的處理,本文介紹各個 Hook 的叫用時機。

# 各階段的鉤子函式

官方製作了一張詳細的生命週期鉤子函式示意圖 (opens new window):

Lifecyle

紅框白底的是各個鉤子函式的名稱,這些鉤子代表 Vue 實體的每個階段,分別的介紹如下:

  • beforeCreate : 實例初始化立即叫用,這時還未創建實例,所以任何 Vue 實體中的設定(例如: data )都還未配置。
  • created : 完成創建實例,這時 Vue 實體中的配置除了 $el 外已全部配置,而 $el 要在掛載模板後才會配置。
  • beforeMount : 在 Vue 實體中的定義被掛載到目標元素之前叫用,這時的 $el 會是還未被 Vue 實體中的定義渲染的初始設定模板。
  • mounted : Vue 實體上的設置已經安裝上模板,這時的 $el 是已經藉由實體中的定義渲染而成的真正的頁面。
  • beforeUpdate : 當實體中的 data 產生變化後或是執行 vm.$forceUpdate() (opens new window) 叫用,這時的頁面還未被重渲染為改變後的畫面。
  • updated : 在重新渲染頁面後叫用,這時的頁面已經被重渲染成改變後的畫面。
  • beforeDestroy : 在此實體被銷毀前時叫用,這時實體還是擁有完整的功能。
  • destroyed : 此實體被銷毀後叫用,這時實體中的任何定義( datamethods...等)都已被解除綁定,代表在此做的任何操作都會失效。

鉤子函式會因為引用了其他的工具(例如: vue-router (opens new window))或是 Vue 實體配置的不同(例如: keep-alive (opens new window))而有所增減。

# 實際演練

接著我們實際來操作各個鉤子函數,然後印出 data$el 看看在各階段會如何變化。

以下是我們使用的例子:

<div id="app">
  {{a}}
</div>
var vm = new Vue({
  el: '#app',
  data: {
    a: 1
  }
});

這個例子在之前的文章中也有使用,只有一個很單純 a 資料以及綁定 Vue 實體到 <div id="app"> 的範例。

下面會將鉤子函數拆成四組來分析,分別是:

  • beforeCreatecreated : 創建實體。
  • beforeMountmounted : 掛載目標元素。
  • beforeUpdateupdated : data 改變後的重渲染。
  • beforeDestroydestroyed : 銷毀實體。

# beforeCreatecreated

在設定中加上 beforeCreatecreated:






 





 








var vm = new Vue({
  el: '#app',
  data: {
    a: 1
  },
  beforeCreate() {
    console.log('Hook: beforeCreate');
    console.log(` this.a: ${this.a}`);
    console.log(` this.$el: ${this.$el}`);
    console.log();
  },
  created() {
    console.log('Hook: created');
    console.log(` this.a: ${this.a}`);
    console.log(` this.$el: ${this.$el}`);
    console.log();
  },
  ...
});

結果如下:

Hook: beforeCreate
 this.a: undefined
 this.$el: undefined

Hook: created
 this.a: 1
 this.$el: undefined

  • beforeCreate : 在 beforeCreate 時因實體還沒創建,所以 a$el 都是 undefined
  • created : 到了 created 時已經創建實例,所以 a 已變為 1 ,但是 $el 因為還未掛載至目標元素,所以依然是 undefined

所以在 beforeCreate 是不能操作實體中的物件的。

# beforeMountmounted

設定中加入 beforeMountmounted :







 




 







var vm = new Vue({
  el: '#app',
  data: {
    a: 1
  },
  ...
  beforeMount() {
    console.log('Hook: beforeMount');
    console.log(this.$el.outerHTML);
    console.log();
  },
  mounted() {
    console.log('Hook: mounted');
    console.log(this.$el.outerHTML);
    console.log();
  },
  ...
});

結果如下:

Hook: beforeMount
<div id="app">
  {{a}}
  <button v-on:click="a++">add</button>
  <button v-on:click="$destroy()">Destroy instance</button>
</div>

Hook: mounted
<div id="app">
  1
  <button>add</button> <button>Destroy instance</button></div>

  • beforeMount : 流程圖上有提到,在叫用 beforeMount 前 Vue 已經決定模板的樣式,所以在 beforeMount 中的 $el 已經有值了,只是它還未依照 Vue 實體上的定義所渲染,只是個初始設定的模板,因此可以看到 {{a}}v-on 這些模板語法都還未被轉換。
  • mounted : 在 mounted 被叫用時已經把 Vue 實體上的定義綁定到元素上,所以這裡看到的是經由 Vue 渲染後的配置。

所以在 beforeMount 前不能操作 DOM 元素。

# beforeUpdateupdated

加上 beforeUpdateupdated 鉤子函數:







 






 









var vm = new Vue({
  el: '#app',
  data: {
    a: 1
  },
  ...
  beforeUpdate() {
    console.log('Hook: beforeUpdate');
    console.log(` this.a: ${this.a}`);
    console.log(` this.$el: ${this.$el}`);
    console.log(this.$el.outerHTML);
    console.log();
  },
  updated() {
    console.log('Hook: updated');
    console.log(` this.a: ${this.a}`);
    console.log(` this.$el: ${this.$el}`);
    console.log(this.$el.outerHTML);
    console.log();
  },
  ...
});

然後在頁面上加顆按鈕來改變 a 的數值:

<div id="app">
  {{a}}
  <button v-on:click="a++">add</button>
</div>

按下 add 按鈕,輸出的結果如下:

Hook: beforeUpdate
 this.a: 2
 this.$el: [object HTMLDivElement]
<div id="app">
  1
  <button>add</button> <button>Destroy instance</button></div>
  
Hook: updated
 this.a: 2
 this.$el: [object HTMLDivElement]
<div id="app">
  2
  <button>add</button> <button>Destroy instance</button></div>

  • beforeUpdate : a 改變後觸發 beforeUpdate ,可以看到 a 已經變為 2 了,可是頁面上還是 1 ,表示這時雖然 data 已經改變,可是還沒有重新渲染畫面。
  • updated : 完成重新渲染的作業後觸發,這時可以看到畫面已經將 1 改為 2了。

updated 時盡量避免修改 data ,這樣有可能再次觸發 update 造成無限循環,如果 data 要連動變化可以使用後面的章節會介紹的 computed (opens new window) 屬性。

# beforeDestroydestroyed

加上 beforeDestroydestroyed 的鉤子函數:







 



 





var vm = new Vue({
  el: '#app',
  data: {
    a: 1
  },
  ...
  beforeDestroy() {
    console.log('Hook: beforeDestroy');
    console.log();
  },
  destroyed() {
    console.log('Hook: destroyed');
    console.log();
  }
});

在頁面上加一個按鈕叫用 $destroy() 銷毀實體:

<div id="app">
  {{a}}
  <button v-on:click="a++">add</button>
  <button v-on:click="$destroy()">Destroy instance</button>
</div>

在按下 Destroy instance 按鈕後,結果如下:

Hook: beforeDestroy

Hook: destroyed
  • beforeDestroy : 叫用 beforeDestroy 表示即將執行銷毀動作,如果有些物件要釋放資源可以在這處理。
  • destroyed : 叫用 destroyed 時,實體已經銷毀。

由於 Vue 會將 Vue 實體綁定在 this 上,所以在 Vue 實例中只要有使用到 this 的函式都不能使用箭頭函數,因箭頭函數的 this 會綁定上層(父親)的內容,所以箭頭函數中的 this 不會是期望的 Vue 實體。

# Demo

# 小結

本篇介紹了 Vue 實體的整個生命週期,並用範例實際操作,演繹在各種不同的鉤子時實體的狀態,這樣對於 Vue 實體的生命週期就不再只是一個概念,而是具體的行為。

有了本篇的知識,在開發時對於要修改資料或是操作 DOM 的時機可以掌握得更好。

# 參考資料