追根溯源:https://cn.vuejs.org/v2/api/#选项-生命周期钩子(中文)
以图为镇:生命周期图示
beforeCreate
官方描述:在实例初始化之后,数据观测(data observer)和event/watcher 事件配置之前被调用。
个人描述:Vue实例已经创建,data、computed、methods、watch、 $el、全部为undefined
代码示例:
var app = new Vue({
el:'#app',
data:{
num:1,
str:'hello'
},
computed: {
aPlus:function () {
return this.num * 2;
}
},
watch:{
person:function(val, oldVal){
console.log(val);
console.log(oldVal);
}
},
methods:{
printStr:function () {
console.log(this.str);
}
},
beforeCreate:function () {
console.log(this); //Vue对象
console.log(this.$data); //undefined
console.log(this.numPlus); //undefined
this.printStr(); // TypeError: this.printStr is not a function
this.$watch('person',function () { //TypeError: Cannot read property 'push' of undefined
console.log('watcher');
});
console.log(this.$el); //undefined
}
})
created
官方描述:在实例创建后被立即调用。在这一步,实例已经完成一下的配置:数据观测(data observer),属性和方法的运 算,watch/event事件回调。然而,挂载阶段还没开始,$el属性目前不可见。
个人描述:data、computed、methods、watch 生成,但$el 为undefined,未挂载,即没有关联dom元素
代码示例:
created:function () {
console.log(this.$data); //data对象
console.log(this.numPlus); // 2
this.printStr(); // method
this.$watch('str',function () { //不报错
console.log('watcher');
});
console.log(this.$el); //undefined
}
beforeMount
官方描述:在挂载开始之前被调用:相关的render函数首次被调用。该钩子在服务器端渲染期间不被调用
个人描述:用template 创建的html,或用render 函数函数创建的html都已经创建完成,但还未挂载。即:有展示无交互状态
注意:1.若无 el 且无 vm.$mount(el),则此函数不调用。详见《生命周期图示》
2.服务器端渲染 对应 浏览器端(客户端)渲染。例如(react)
代码示例:
<body>
<div id="app" class="app-div">
<p ref="p">hello</p>
</div>
<script src="index.js"></script>
</body>
beforeMount:function () {
console.log(this.$el); //<div id="app" class="app-div"><p ref="p">hello</p></div>
console.log(this.$refs.p) //undefined
}
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
})
}
个人总结:若存在属性 el ,mounted 会顺势在 beforeMount 调用完成后直接调用一次,并且此时已经完成了挂载。当 el 改变了,会重新调用新的 mounted 。mounted只对应 el (根元素),所以想要等这个视图都渲染完毕,依照官方介绍些。
代码示例:
<body>
<div id="app" class="app-div">
<p ref="p">hello</p>
<button v-on:click="mountEl">mountEL</button>
</div>
<script src="index.js"></script>
</body>
var MyComponent = Vue.extend({
template: '<div>World!</div>',
mounted:function () {
console.log('second'); //点击 'mountEl' 按钮后打印
}
})
var app = new Vue({
el:'#app',
data:{ count: 0},
methods:{
mountEl:function () {
new MyComponent().$mount('#app');
}
},
mounted:function () {
console.log(this.$refs.p); //<p>hello</p>
console.log('first');
}
})
updated
官方描述:由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件DOM已经更新,所以你现在可以执行依赖于DOM的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或watcher取而代之。注意:updated不会承诺所有的子组件也都一起被重绘。如果你希望等到整个视图都重绘完毕,可以用 vm.$nextTick替换掉 updated。该钩子在服务器端渲染期间不被调用。
个人总结:当和页面渲染相关的data数据发生变化的时候调用。且当此函数可调用时,发生变化的dom已经更新完成了。
代码示例:
<body>
<div id="app" class="app-div">
<p ref="p">{{count}}</p>
<button v-on:click="count++">update</button>
</div>
<script src="index.js"></script>
</body>
var app = new Vue({
el:'#app',
data:{
count: 0
},
updated:function () {
console.log(this.$refs.p);//<p>1</p> 每次点击'update'按钮count+1,并打印
console.log('update');
}
})
activated
官方描述:keep-alive组件激活时调用。该钩子在服务器端渲染期间不被调用。
个人总结:所谓keep-alive组件激活,说明activated是放在组件中的。当组件是keep-alive状态的时候调用,同时,当组件有keep-alive包裹时,首次调用,也会触发。
代码实例:(官方keep-alive的例子很漂亮,这里引用了官方的例子)
<body>
<div id="dynamic-component-demo">
<button
v-for="tab in tabs"
v-bind:key="tab"
v-bind:class="['tab-button', { active: currentTab === tab }]"
v-on:click="currentTab = tab"
>{{ tab }}</button>
<keep-alive>
<component
v-bind:is="currentTabComponent"
class="tab"
></component>
</keep-alive>
</div>
<script src="index.js"></script>
</body>
Vue.component('tab-posts', {
data: function () {
return {
posts: [
{
id: 1,
title: 'Cat Ipsum',
content: '<p>Dont wait</p>'
},
{
id: 2,
title: 'Hipster Ipsum',
content: '<p>Bushwick </p>'
},
{
id: 3,
title: 'Cupcake Ipsum',
content: '<p>Icing </p>'
}
],
selectedPost: null
}
},
template: `
<div class="posts-tab">
<ul class="posts-sidebar">
<li
v-for="post in posts"
v-bind:key="post.id"
v-bind:class="{ selected: post === selectedPost }"
v-on:click="selectedPost = post"
>
{{ post.title }}
</li>
</ul>
<div class="selected-post-container">
<div
v-if="selectedPost"
class="selected-post"
>
<h3>{{ selectedPost.title }}</h3>
<div v-html="selectedPost.content"></div>
</div>
<strong v-else>
Click on a blog title to the left to view it.
</strong>
</div>
</div>
`, //注意,这里用的是 v-bind:is 绑定
activated:function () {
console.log('activated');//首次进入会调用 其次,每次切换回此tab页也回调用。
},
deactivated:function () {
console.log('deactivated'); //切换出此tab页时调用
}
})
Vue.component('tab-archive', {
template: '<div>Archive component</div>'
})
new Vue({
el: '#dynamic-component-demo',
data: {
currentTab: 'Posts',
tabs: ['Posts', 'Archive']
},
computed: {
currentTabComponent: function () {
return 'tab-' + this.currentTab.toLowerCase()
}
},
})
deactivated
官方描述:keep-alive组件停用时调用。该钩子在服务器端渲染期间不被调用。
个人总结:由keep-alive包裹的组件失效的时候调用,用 activated 的例子来说,就是切换出去(此组件失效)的时候调用
代码示例:同 activited 代码示例
beforeDestroy
官方描述:实例销毁之前调用。在这一步,实例仍然完全可用。该钩子在服务器端渲染期间不被调用。
个人总结:实例销毁的前一步调用,此时实例还是完好的。销毁包括:使用v-for,v-if 控制子组件的生命周期,用vm.$destroy销毁等,官方推荐使用 v-if,v-for 指令以数据驱动的方式控制子组件的生命周期。注意销毁的是哪个组件就调用哪个组件的对应beforeDestroy方法。destroyed同理。
代码示例:
<body>
<div id="app" class="app-div">
<p ref="p">{{count}}</p>
<button v-on:click="destroyClick">destroy</button>
</div>
<script src="index.js"></script>
</body>
var app = new Vue({
el:'#app',
data:{
count: 0
},
methods:{
destroyClick:function () {
this.$destroy();
},
printTest:function () {
console.log('print');
}
},
beforeDestroy:function () {
console.log('beforeDestroy');
console.log(this.$refs.p); //<p>0</p>
}
})
destroyed
官方描述:实例销毁之后调用。调用后,Vue实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会 被销毁。该钩子在服务器端渲染期间不被调用。
个人总结:此时Vue实例,data,el,method 都还存在,但挂载已经不在了,即无数据绑定,无法获取与dom的关联。
代码示例:
<body>
<div id="app" class="app-div">
<p ref="p">{{count}}</p>
<button v-on:click="destroyClick">destroy</button>
</div>
<script src="index.js"></script>
</body>
var app = new Vue({
el:'#app',
data:{
count: 0
},
methods:{
destroyClick:function () {
this.$destroy();
},
printTest:function () {
console.log('print');
}
},
destroyed:function () {
console.log('destroyed');
console.log(this); //Vue实例
console.log(this.$data); //data 对象
console.log(this.$el); // <div id="app" class="app-div"><p>0</p> <button>destroy</button></div>
this.printTest(); //print
console.log(this.$refs.p); //undefined
}
})
errorCaptured
官方描述:当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。你可以在此钩子中修改组件的状态。因此在模版或渲染函数中设置其他内容的短路条件非常重要,他可以防止当一个错误被捕获时该组件进入一个无限循环的渲染循环。 错误传播规则:1.默认情况下,如果全局的 config.errorHandler 被定义,所有的错误仍然会发送它,因此这些错误仍然会向单一的分析服务的地方进行汇报。2.如果一个组件的继承或父级从属链路中存在多个 errorCaptured 钩子,则他们将会被相同的错误逐个唤起。3.如果此 errorCaptured 钩子自身抛出了一个错误,则这个新错误和原本被捕获的错误都会发送给全局的 config.errorHandler.
个人总结:这个钩子是用来监控错误的,具体示例可以看代码,这里提醒一句的是注意官方描述中的:错误传播规则。
代码示例:
<body>
<div id="app" class="app-div">
<p ref="p">{{count}}</p>
<button v-on:click="destroyClick">destroy</button>
<object-props></object-props>
</div>
<script src="index.js"></script>
</body>
Vue.component('age',{
props:{
age:{ default: 18 }
},
template:'<div>{{age}}</div>',
mounted:function () {
throw Error('age');
}
})
Vue.component('object-props',{
props:{
name:{ default: '16522' },
ifShow:{ default: true },
},
template:'<div>name:{{name}} <age v-if="ifShow"></age></div>',
mounted:function () {
throw Error('object-props');
},
errorCaptured:function (error) {
console.log('object-props-errorCaptured');
console.log(error); //能捕获到子组件中的错误
this.props.ifShow = false;
}
})
Vue.config.errorHandler = function (err, vm, info) { //若没有此函数,会在控制台报错。捕捉到所有报错,即使组件中没有errorCaptured钩子。可以和errorCaptured钩子同步使用
console.log('errorHandler');
console.log(err);
}
var app = new Vue({
el:'#app',
data:{
count: 0
},
methods:{
destroyClick:function () {
this.$destroy();
},
printTest:function () {
console.log('print');
}
},
errorCaptured:function (error) {
console.log('errorCaptured');
console.log(error); //此处回捕获<object-props>抛出的异常 不管子组件中某一层的组件没有errorCaptured钩子,这里能捕获到子组件全部的Error
}
})
整体总结:
1.在非服务端渲染的情况下 : beforecreate -> created -> beforeMount -> mounted ... beforeDestroy ->destroyed 顺序执行
2.这些钩子只是钩子而已,他们本质上无法阻止渲染的过程(除非你在钩子中手动阻止),更具这些钩子可以得知渲染的过程,详见:生命周期图示。