Vue 組件生命週期鉤子函數
所謂生命週期鉤子函數(簡稱生命週期函數),指的是組件的創建、更新、銷燬三個階段所觸發執行的函數。根據每個階段觸發的鉤子函數,我們可以相應的做一些操作,如獲取後端接口數據、監聽事件、執行事件、執行定時器、移除事件、清理定時器等等。
生命週期根據上面的三個階段,本人總結爲:
- 實例化期
組件創建
- 存在期
組件更新
- 銷燬期
組件銷燬
後續會根據這三大週期,分別說明生命週期函數。
爲了方便理解,本人在 http://jsrun.net 上編寫了例子。(一個跟 jsfiddle 差不多的網站,國內 jsfiddle 被牆了)
生命週期示意圖
首先看下官網的生命週期示意圖,這裏的生命週期函數都是針對瀏覽器端的,服務端目前只支持 beforeCreate
和 created
這兩個生命週期函數。
其中官網並沒有把 render
、renderError
函數歸納爲生命週期鉤子函數。
其中 Has "el" option
對比如下:
有 el
// 有el屬性的情況下
new Vue({
el: "#app",
beforeCreate: function() {
console.log("調用了beforeCreate");
},
created: function() {
console.log("調用了created");
},
beforeMount: function() {
console.log("調用了beforeMount");
},
mounted: function() {
console.log("調用了mounted");
}
});
// 輸出結果
// 調用了beforeCreate
// 調用了created
// 調用了beforeMount
// 調用了mounted
無 el
// 有el屬性的情況下
const vm new Vue({
beforeCreate: function() {
console.log("調用了beforeCreate");
},
created: function() {
console.log("調用了created");
},
beforeMount: function() {
console.log("調用了beforeMount");
},
mounted: function() {
console.log("調用了mounted");
}
});
// 輸出結果
// 調用了beforeCreate
// 調用了created
無 el 時,如果需要掛載,可以這樣處理:vm.$mount('#app')
。效果一樣了,本質上沒區別,只是用法更靈活。
實例化期
實例化期會涉及到以下生命週期函數(執行順序自上而下):
- beforeCreate
- created
- beforeMount
- mouted
其中 beforeCreate
和 created
中間會觸發 render
函數,如果有 template 會轉換爲 render 函數進行渲染。(當然如果組件的 定義了 render 函數,那麼 render 函數優先級更高)
詳細的例子請看 http://jsrun.net/LZyKp/edit
// 輸出請看 右下角 Console 命令行工具
new Vue({
el: '#dynamic-component-demo',
data: {
num: 2,
},
beforeCreate(){
console.log("beforeCreate",this.num,this.a);
// 輸出爲 befoerCreate,,
// this.num 數據還沒監測,this.a 方法未綁定
},
created(){
console.log("created",this.num,this.a,this.$el);
// 輸出爲 created, 2, function () { [native code] }
},
beforeMount(){
console.log(this.$el.innerText);
// 輸出 {{ num }},還是原來的 DOM 內容
},
mounted(){
console.log(this.$el.innerText);
// 輸出 2,已經是 vue 渲染的 DOM 內容
},
methods: {
a(){}
}
})
beforeCreate
在實例初始化之後,數據觀測 (data observer) 和 event/watcher 事件配置之前被調用。
created
在實例創建完成後被立即調用。在這一步,實例已完成以下的配置:數據觀測 (data observer),屬性和方法的運算,watch/event 事件回調。然而,掛載階段還沒開始,$el
屬性目前不可見。
beforeMount
在掛載開始之前被調用:相關的 render
函數首次被調用。$el
屬性已經可見,但還是原來的 DOM,並非是新創建的。
mounted
el
被新創建的 vm.$el
替換,並掛載到實例上去之後調用該鉤子。如果 root 實例掛載了一個文檔內元素,當 mounted
被調用時 vm.$el
也在文檔內。
注意 mounted
不會承諾所有的子組件也都一起被掛載。如果你希望等到整個視圖都渲染完畢,可以用 vm.$nextTick 替換掉 mounted
:
mounted: function () {
this.$nextTick(function () {
// Code that will run only after the
// entire view has been rendered
})
}
存在期
存在期會涉及到以下生命週期函數:
- beforeUpdate
- updated
Vue 需要改變數據纔會觸發組件重新渲染,纔會觸發上面的存在期鉤子函數。其中 beforeUpdate
和 updated
中間會觸發 render
函數。
例子請看 http://jsrun.net/8ZyKp/edit。
// 需要點擊更新按鈕
// 連續點擊更新按鈕,都會是 2 秒後不點擊更新纔會輸出 “2 秒沒更新了”
// 輸出請看 右下角 Console 命令行工具
new Vue({
el: '#dynamic-component-demo',
data: {
num: 2,
},
beforeUpdate(){
clearTimeout(this.clearTimeout);
this.clearTimeout = setTimeout(function(){
console.log("2 秒沒更新了");
},2000);
console.log("beforeUpdate",this.num,this.$el.innerText);
// 第一次點擊更新,輸出爲 beforeUpdate,3,點擊更新 2
},
updated(){
console.log("updated",this.num,this.$el.innerText);
// 第一次點擊更新,輸出爲 updated,3,點擊更新 3
},
methods: {
updateComponent(){
this.num++;
}
}
})
beforeUpdate
數據更新時,虛擬 DOM 變化之前調用,這裏適合在更新之前訪問現有的 DOM,比如手動移除已添加的事件監聽器。
請不要在此函數中更改狀態,否則會觸發死循環。
updated
數據更新和虛擬 DOM 變化之後調用。
當這個鉤子被調用時,組件 DOM 已經更新,所以你現在可以執行依賴於 DOM 的操作。然而在大多數情況下,你應該避免在此期間更改狀態。如果要相應狀態改變,通常最好使用計算屬性或 watcher 取而代之。
和 mounted 一樣, updated
不會承諾所有的子組件也都一起被重繪。如果你希望等到整個視圖都重繪完畢,可以用 vm.$nextTick 替換掉 updated
:
updated: function () {
this.$nextTick(function () {
// Code that will run only after the
// entire view has been re-rendered
})
}
請不要在此函數中更改狀態,否則會觸發死循環。
銷燬期
銷燬期會涉及到以下生命週期函數:
- beforeDestroy
- destroyed
例子請看 http://jsrun.net/QZyKp/edit。
// 切換 tab,看右下角 console 輸出
Vue.component('tab-home', {
template: '<div>Home component</div>',
beforeDestroy(){
console.log("tab-home","beforeDestroy");
},
destroyed(){
console.log("tab-home","destroyed");
},
})
Vue.component('tab-posts', {
template: '<div>Posts component</div>',
beforeDestroy(){
console.log("tab-posts","beforeDestroy");
},
destroyed(){
console.log("tab-posts","destroyed");
},
})
Vue.component('tab-archive', {
template: '<div>Archive component</div>',
beforeDestroy(){
console.log("tab-archive","beforeDestroy");
},
destroyed(){
console.log("tab-archive","destroyed");
},
})
new Vue({
el: '#dynamic-component-demo',
data: {
currentTab: 'Home',
tabs: ['Home', 'Posts', 'Archive']
},
computed: {
currentTabComponent: function () {
return 'tab-' + this.currentTab.toLowerCase()
}
}
})
beforeDestroy
實例銷燬之前調用,在這一步,實例仍然完全可用。一般在這裏移除事件監聽器、定時器等,避免內存泄漏
destroyed
Vue 實例銷燬後調用。調用後,Vue 實例指示的所有東西都會解綁定,所有的事件監聽器會被移除,所有的子實例也會被銷燬。
所以如果需要用到 Vue 實例指示的所用綁定的東西,需要在 beforeDestroy 中使用。這麼說,destroyed 函數能做的事,在 beforeDestroy 也能做,所以沒必要在 destroyed 函數中處理。
其他不常用的生命週期函數
- activated
當組件激活的時候調用,可以參考構建組件 - keep-alive
- deactivated
當組件停用的時候調用,可以參考構建組件 - keep-alive
- errorCaptured
這個生命鉤子詳細請看官網,2.5.0 新增的,當捕獲一個來自子孫組件的錯誤時被調用。
使用注意
生命週期函數請不要使用 ES6 箭頭函數,否則 this 指向會有問題。
請看這個例子 http://jsrun.net/cZyKp/edit。
// 輸出請看 右下角 Console 命令行工具
new Vue({
el: '#dynamic-component-demo',
data: {
num: 2,
},
created: ()=>{
console.log("created",this);
// 輸出爲 created,[object Window]
// this 指向不是 Vue 實例而是父級 this
}
})