Vue 的生命周期:beforeCreate,created,beforeMount,beforeUpdate, mounted,beforeUpdate,updated,updated, beforeDestroy,destroy
主要参考来源:
Vue 的生命周期是个啥? 每个 Vue 组件都是一个个的 Vue 实例,当一个 Vue 实例被创建时,都会有一系列的初始化过程,每个过程都会执行一些特定的方法。 学过 JAVA 或者 PHP 的都应该知道,对象在被实例化和被销毁的时候也有一个过程,期间会调用构造函数,析构函数;这个和 Vue 生命周期类似。
官网的示意图
复习每个生命周期钩子
beforeCreate(创建前) 在实例初始化之后,进行数据侦听和事件/侦听器的配置之前同步调用。 此时组件上的对象还未被创建,el 和 data 都没有初始化,也不能调用 methods,data,computed,等方法和数据
create(创建后) 在实例创建完成后被立即同步调用。在这一步中,实例已完成对选项的处理,意味着以下内容已被配置完毕:数据侦听、计算属性、方法、事件/侦听器的回调函数。然而,挂载阶段还没开始,且 $el property 目前尚不可用。 总之就是,除了页面还没有开始渲染,其他的工作已经准备就绪,包括数据观测,树形和方法调用,watch/event事件回调,data初始化。可以进行 ajax 请求数据了,但是最好不要 ,毕竟此时的页面还是空白
beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用。 实例已完成以下的配置: 编译模板,把data里面的数据和模板生成html,完成了el和data 初始化,但是此时还没有挂在html到页面上,还是没有 dom。
mounted 挂载完成了,html已经渲染到了document。可以进行ajax请求了。 官方温馨提示: mounted 不会保证所有的子组件也都被挂载完成。如果你希望等到整个视图都渲染完毕再执行某些操作,可以在 mounted 内部使用 vm.$nextTick
beforeUpdate 数据更新之后,dom 被更新之前调用。注意官方的一句话:这里适合在现有 DOM 将要被更新之前访问它,比如移除手动添加的事件监听器
updated 在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。这个时候dom已经更新完毕,可以进行依赖于dom的操作,但是,你应该避免在此期间更改状态。如果要相应状态改变,因为这可能会导致更新无限循环
beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用。
这一步还可以用this来获取实例
一般在这一步做一些重置的操作,比如清除掉组件中的定时器 和 监听的dom事件
destroyed 实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁
以上复习了生命周期钩子函数在各个阶段都干了什么。然后用实例来验证加强记忆一下,顺便探讨父子组件的生命周期表现是什么样的。
单组件的生命周期 简单两个组件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 <template > <div class ="main" > <span id ="tip_text" > 我是一个测试生命周期的组件</span > </div > </template > <script > export default { name: 'LifeCycle' , data () { return { name: '我是谁' } }, beforeCreate ( ) { console .log('**** parent beforeCreate' ); console .log('**** parent beforeCreate -this.name:' , this .name); console .log('**** parent beforeCreate -test dom:' , document .getElementById('tip_text' )); }, created ( ) { console .log('**** parent created' ); console .log('**** parent created -this.name:' , this .name); console .log('**** parent created -test dom:' , document .getElementById('tip_text' )); }, beforeMount ( ) { console .log('**** parent beforeMount' ); console .log('**** parent beforeMount -this.name:' , this .name); console .log('**** parent beforeMount -test dom:' , document .getElementById('tip_text' )); }, mounted ( ) { console .log('**** parent mounted' ); console .log('**** parent mounted -this.name:' , this .name); console .log('**** parent mounted -test dom:' , document .getElementById('tip_text' )); }, beforeUpdate ( ) { console .log('**** parent beforeUpdate' ); console .log('**** parent beforeUpdate -this.name:' , this .name); console .log('**** parent beforeUpdate -test dom:' , document .getElementById('tip_text' )); }, updated ( ) { console .log('**** parent updated' ); console .log('**** parent updated -this.name:' , this .name); console .log('**** parent updated -test dom:' , document .getElementById('tip_text' )); }, beforeDestroy ( ) { console .log('**** parent beforeDestroy' ); console .log('**** parent beforeDestroy -this.name:' , this .name); console .log('**** parent beforeDestroy -test dom:' , document .getElementById('tip_text' )); }, destroyed ( ) { console .log('**** parent destroyed' ); console .log('**** parent destroyed -this.name:' , this .name); console .log('**** parent destroyed -test dom:' , document .getElementById('tip_text' )); }, methods: {} } </script >
模拟页面进入,控制台输出如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 **** parent beforeCreate **** parent beforeCreate -this.name: undefined **** parent beforeCreate -test dom: null **** parent created **** parent created -this.name: 我是谁 **** parent created -test dom: null **** parent beforeMount **** parent beforeMount -this.name: 我是谁 **** parent beforeMount -test dom: null **** parent mounted **** parent mounted -this.name: 我是谁 **** parent mounted -test dom: <span id="tip_text">我是一个测试生命周期的组件</span>
从上面可以看到,组件在创建的时候
依次执行:beforeCreate > created > beforeMount > mounted;
beforeCreate 期间,不能访问data,而 created之后就可以访问 data 了;
mounted 时期,不仅能访问data,dom元素也可以正确获取;
切换路由,模拟页面离开,控制台输出如下: 1 2 3 4 5 6 **** parent beforeDestroy **** parent beforeDestroy -this.name: 我是谁 **** parent beforeDestroy -test dom: null **** parent destroyed **** parent destroyed -this.name: 我是谁 **** parent destroyed -test dom: null
组件离开被销毁时候,验证出以下观点
依次执行:beforeDestroy > destroyed;
beforeDestroy 期间,还能访问 data,但是 dom 此时已经被销毁了;
destroyed 时期,竟然还能访问data?有点疑惑 ;
发现updated和beforeUpdate并没有参与组件的创建和销毁,尝试加入动作;
调整组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <template > <div class ="main" > <span > 我是一个测试生命周期的组件</span > <div > <span id ="tip_text" > 点击测试 {{ num }} </span > <button @click ="onClickCount" > 点击</button > </div > </div > </template > <script > { data () { return { name: '我是谁' , num: 1, } }, methods: { onClickCount ( ) { this .num++; } } } </script >
1 2 3 4 5 6 **** parent beforeUpdate **** parent beforeUpdate -this.name: 我是谁 **** parent beforeUpdate -test dom: <span id="tip_text">点击测试 3 </span> **** parent updated **** parent updated -this.name: 我是谁 **** parent updated -test dom: <span id="tip_text">点击测试 3 </span>
组件嵌套的生命周期会如何表现呢? 先来两个组件
父组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 <template> <div class="main"> <span>我是一个测试生命周期的组件</span> <div> <span>输入的内容:{{ title || '-' }}</span> </div> <SlInput @slInput="onSlInput" :title="title"/> </div> </template> <script> import SlInput from './SlInput.vue' export default { name: 'LifeCycle', components: { SlInput }, data () { return { name: '我是谁', title: '', num: 1, } }, beforeCreate() { console.log('**** parent beforeCreate'); }, created() { console.log('**** parent created'); }, beforeMount() { console.log('**** parent beforeMount'); }, mounted() { console.log('**** parent mounted'); }, beforeUpdate() { console.log('**** parent beforeUpdate'); }, updated() { console.log('**** parent updated'); }, beforeDestroy() { console.log('**** parent beforeDestroy'); }, destroyed() { console.log('**** parent destroyed'); }, methods: { onSlInput(value) { this.title = value } } } </script>
子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <template> <input type="text" :value="title" @input="onInput"> </template> <script> export default { name: 'SlInput', data() { return {} }, props: ['title'], beforeCreate() { console.log('**** children beforeCreate'); }, created() { console.log('**** children created'); }, beforeMount() { console.log('**** children beforeMount'); }, mounted() { console.log('**** children mounted'); }, beforeUpdate() { console.log('**** children beforeUpdate'); }, updated() { console.log('**** children updated'); }, beforeDestroy() { console.log('**** children beforeDestroy'); }, destroyed() { console.log('**** children destroyed'); }, methods: { onInput(event) { this.$emit('slInput', event.target.value) } } } </script>
同样再次模拟页面进入,控制台输出如下: 1 2 3 4 5 6 7 8 **** parent beforeCreate **** parent created **** parent beforeMount **** children beforeCreate **** children created **** children beforeMount **** children mounted **** parent mounted
可以发现,父子组件嵌套情况下,组件实例化时:
父组件先进行实例化,在模板挂载之前,进行子组件实例化,父组件会等待子组件实例化完毕,再进行父组件自己的模板挂载 beforeCreate > created > beforeMount > children-beforeCreate > children-created > children-beforeMount > children-mounted > mounted
模拟页面离开,控制台输出如下: 1 2 3 4 **** parent beforeDestroy **** children beforeDestroy **** children destroyed **** parent destroyed
父子组件嵌套,父组件在销毁时:
父组件先准备进行销毁,进入 调用钩子 beforeDestroy,等待子组件销毁完毕,父组件继续执行销毁,调用钩子 destroyed,执行顺序如下: parent beforeDestroy > children-beforeDestroy > children-destroyed > destroyed
何时调用 update 呢? 尝试子组件和父组件通信,修改父组件的 data 1 2 3 4 **** parent beforeUpdate **** children beforeUpdate **** children updated **** parent updated
尝试父组件修改子组件data 父组件组件添加以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <div> <button @click="onClickSetChildrenCount">点击修改子组件data</button> </div> <script> { methods: { onClickSetChildrenCount() { this.$refs.SlInput.childrenCount++ } } } </script>
子组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 <div> 子组件data修改测试:{{ childrenCount }} </div> <script> { data() { return { childrenCount: 1 } } } </script>
点击按钮触发,修改子组件的data,控制台输出如下
1 2 **** children beforeUpdate **** children updated
此时没有父组件的参与
单修改父组件data,会触发子组件的 update 吗? 还是那个点击测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <div> <span id="tip_text">点击测试 {{ num }} </span> <button @click="onClickCount">点击</button> </div> <script> { data() { return { childrenCount: 1 } } methods: { onClickCount() { this.num++; }, } } </script>
点击触发事件,控制台输出
1 2 **** parent beforeUpdate **** parent updated
此时没有子组件的参与
子组件创建和销毁,父组件生命周期是个什么表现? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <template> <div class="main"> <h1>我是一个测试生命周期的组件</h1> <hr /> 子组件在这: <SlInput id="tip_text" :title="title" v-if="isShow" /> <hr /> <div> <button @click="onClickToggle">点击{{ isShow ? '隐藏' : '显示' }}</button> </div> </div> </template> <script> { data() { return { isShow: true } } methods: { onClickToggle() { this.isShow = !this.isShow }, } } </script>
点击隐藏子组件
1 2 3 4 5 6 **** parent beforeUpdate **** parent beforeUpdate -test dom: <div id="tip_text">…</div> **** children beforeDestroy **** children destroyed **** parent updated **** parent updated -test dom: null
模拟子组件销毁,可以看出:
子组件销毁之前,父组件进入 beforeUpdate;
等子组件销毁完毕,继续进行父组件的 updated,父组件更新完毕;
点击显示子组件
1 2 3 4 5 6 7 8 **** parent beforeUpdate **** parent beforeUpdate -test dom: null **** children beforeCreate **** children created **** children beforeMount **** children mounted **** parent updated **** parent updated -test dom: <div id="tip_text">…</div>
父组件内,模拟初始化子组件,可以看出:
子组件实例化之前,父组件进入 beforeUpdate;
等子组件挂载完毕,继续进行父组件的 updated,父组件更新完毕;
组件嵌套时,生命周期总结
子组件的创建销毁会触发父组件的 update;
子组件的 update,不一定会触发父组件的 update;
父组件的 update,不一定会触发子组件的 update;