Vue鉤子函數
在開發Vue組件時,鉤子函數我們會經常用到,但是具體在什么時機,使用哪個鉤子函數,會產生什么樣的結果,總會模棱兩可。有時某一個不好用就換另一個,好用后也不深究原因,這里就借這個機會,好好聊一聊鉤子函數那些事。
生命周期圖示
1. beforeCreate 和 Created
下面是Vue
源碼中調用beforeCreate
和Created
的時機
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
從源碼中可以看到,在beforeCreate之前
執行了initLifecycle
, initEvents
, initRender
,之后才對data和props進行了注入、觀測。所以 data
, props
, 以及與之相關的methods
, watch
, computed
在created
之后才可以正常發揮作用。
export default {
beforeCreate() {
console.log(this.msg, this.sayHello); //undefined, undefined
},
created() {
console.log(this.msg, this.sayHello); // hello Vue, ...
},
data () {
return {
msg: 'hello Vue'
}
},
methods: {
sayHello() {
console.log('hello Vue');
}
}
}
從代碼的運行結果可以看到,在beforeCreate中msg,sayHello為undefined,created中則正確輸出。
了解以上差別后,我們可以在beforeCreate
中做一些和data
無關的操作,需要操作data
可以在created
中進行,比如異步獲取數據后賦值。
2. beforeMonut 和 mounted
created
階段后數據觀察就準備完畢了,接下來就是模版的處理。
<body>
<div id="app">
</div>
</body>
<script src='./vue.min.js'></script>
<script>
new Vue({
el: '#app',
data:{
msg: 'hello Vue'
},
template: '<div><span>hello Vue</span><input type="text"></div>',
created() {
console.log(this.$el)
// 輸出:undefined
},
beforeMount() {
console.log(this.$el)
// 輸出:<div id='app'></div>
},
mounted() {
console.log(this.$el)
/*輸出:<div>
<span>hello Vue</span><input type="text">
</div>*/
}
})
</script>
從代碼的運行結果可以看出`mount`過程,在`created`中`this.$el`還為`undefined`,在beforeMount中,this.$el為Vue實例中的配置的el,并沒有獲得模版對應的節點。之后對模版進行編譯,并用編譯后的DOM替換el,插入到頁面,在mounted中可以獲得真實的DOM節點,并可以對其進行操作。我們經常會在vue中使用jQuery插件,jQuery操作的是真實的DOM,所以插件的初始化應該在mounted中進行。
3. beforeUpdate 和 updated
mounted
之后,頁面就展示出來了。由于Vue
是數據驅動,當我們進行的操作更改了data
中的值,頁面DOM
也會發生響應的變化。如果想要在update
過程中進行一些操作,就要用到beforeUpdate和updated。
```html
<body>
<div id="app">
</div>
</body>
<script src='./vue.min.js'></script>
<script>
new Vue({
el: '#app',
data:{
msg: 'hello Vue',
count: 10,
},
template: '<div><span>{{msg}}</span><input type="text" v-model="msg"> <span>{{count}}</span></div>',
beforeUpdate() {
this.count = 100;
console.log(this.$el.innerHTML);
// <span>hello Vue</span><input type="text"> <span>10</span>
// <span>hello Vu</span><input type="text"> <span>10</span>
},
updated() {
console.log(this.$el.innerHTML);
// <span>hello Vu</span><input type="text"> <span>100</span>
}
})
</script>
...
當我們通過input改變msg來觸發update過程。并且在beforeUpdate中進一步更改了count的值。這樣beforeUpdate運行了兩次,而在這個函數中虛擬DOM還沒有開始渲染,所以即使改變count值為100,輸出依然時舊值10。updated在組件DOM更新完成時觸發。如果你的操作依賴于更新后的DOM,就應該在此時進行。但是要注意盡量不要在updated中再次改變data中的值,這會再次觸發更新操作,比較容易導致死循環。
4. beforeDestroy 和 destroyed
當我們不再需要一個組件,可以執行$destroy銷毀它。在beforeDestroy中,組件一切都是正常的,可以進行最后的操作。下面是Vue源碼中調用beforeDestroy和destoryed的間隔中,執行的操作。
callHook(vm, 'beforeDestroy');
vm._isBeingDestroyed = true;
// remove self from parent
var parent = vm.$parent;
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm);
}
// teardown watchers
if (vm._watcher) {
vm._watcher.teardown();
}
var i = vm._watchers.length;
while (i--) {
vm._watchers[i].teardown();
}
// remove reference from data ob
// frozen object may not have observer.
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--;
}
// call the last hook...
vm._isDestroyed = true;
// invoke destroy hooks on current rendered tree
vm.__patch__(vm._vnode, null);
// fire destroyed hook
callHook(vm, 'destroyed');
從源碼中的注釋中了解到,銷毀操作包括一下事情的處理:
1. remove self from parent (從父組件中移除)
2. teardown watchers (移除watchers)
3. remove reference from data observer (從observer中移除引用)
4. invoke destroy hooks on current rendered tree (處理DOM tree)
5. fire destroyed hook (觸發destroyed)
至此,一個組件的生命周期就算結束了。
總結:
通過以上的分析,對一個Vue實例的生命周期便有了一個清晰的認識。從實例化開始(beoforeCreate)到銷毀(destroyed),以及各個時期可以對哪些對象進行操作。最后附上官網的生命周期圖示,相信閱讀完文章,再看此圖,會獲得更好的理解