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),以及各个时期可以对哪些对象进行操作。最后附上官网的生命周期图示,相信阅读完文章,再看此图,会获得更好的理解