一文教你搞懂Vue生命周期

本文详细介绍了Vue3组件的完整生命周期过程,包括创建、初始化、挂载、更新、激活与销毁阶段,以及如何正确操作DOM和选择ajax调用时机,还探讨了CompositionAPI和生命周期函数的使用。
摘要由CSDN通过智能技术生成

Vue生命周期

生命周期示意图

img

Vue 实例生命周期

Vue3

组件生命周期图示

组件创建阶段

  • new vue

    new一个vue的实例对象;此时会进入组件的创建过程(该组件在代码中被注册并使用时,就代表着其被new了一个新的实例对象)。

  • Init Events & Lifecycle

    初始化组件的事件和生命周期函数;当执行完这一步之后,组件的生命周期函数就已经全部初始化好了,等待着依次去调用。

  • beforeCreate (服务端渲染可用

    (初始化一个空的 Vue 实例),组件的props,data和methods以及页面DOM结构,都还没有初始化

    在实例初始化之后,进行数据侦听事件/侦听器的配置之前同步调用

  • Init injections & reactivity

    这个阶段中,正在初始化props,computed, data和methods中的数据以及方法。

    在creted之前就会行初始化 computed 和 watch

  • created(服务端渲染可用

    Vue 实例初始化完成,props data methods 都已初始化完成,可调用。但尚未开始渲染模板。

    在实例创建完成后被立即同步调用。在这一步中,实例已完成对选项的处理,意味着以下内容已被配置完毕:数据侦听计算属性方法事件/侦听器的回调函数。然而,挂载阶段还没开始,且 $el property 目前尚不可用。

  • 判断是否有el,template,然后进行编译

    正在解析模板结构,把data上的数据拿到,并且解析执行模板结构汇总的指令;当所有指令被解析完毕,那么模板页面就被渲染到内存中了;当模板编译完成,我们的模板页面,还没有挂载到页面上,只是存在于内存中,用户看不到页面;

image-20240308101931045

优先级顺序:el < template < render

el对应的HTML元素是写在网页上的。

//1、el,template,render(渲染函数)都是vue对象对应的HTML元素(DOM对象)
//2、优先级顺序:el < template < render
//3、el对应的HTML元素是写在网页上的。
 
 
HTML代码:
 
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<div id="app">
			<span>我今年{{age}}岁了</span>
		</div>
	</body>
</html>
<script type="text/javascript" src="js/vue.min.js" ></script>
//1、只有el
let vm = new Vue({
	el:"#app",
	data:{
		age:12
	}
});
2、只有template,是不行的,因为,vue对象不知道把template放在何处;
3、只有render(渲染)函数,也是不行的,因为,vue对象不知道把render后的结果放在何处;
4、既有el又有template,就会用template里的内容替换el的outHTML。
 
let vm = new Vue({
	el:"#app",
	template:"<div><p>我template出来的,年龄{{age}}</p></div>",
	data:{
		age:12
	}
});
查看elements:
 
发现 id为app的div没有了
 
 
5、既有el又有template,又有render函数。就会使用render函数的内容,因为它的优先级高。
//   但是此时,“Mustache”语法 (双大括号)没法使用,
//   因为,vue只是把render函数的返回值放在HTML里,而不进行再次的绑定
//   render函数就是让你发挥 JavaScript 最大的编程能力。
 
let vm = new Vue({
	el:"#app",
	template:"<div><p>我template出来的,年龄{{age}}</p></div>",
	data:{
		age:12
	},
	render:function(createElement){
	  // return createElement('h1', '我是render出来的HTML,年龄{{age}}');//不能使用“Mustache”语法 (双大括号)
	   return createElement('h1', '我是render出来的HTML,年龄'+this.age);
	}
});
查看elements:
 
发现 id为app的div没有了,template属性的值也没有起作用,只显示了render函数的返回值。
  • beforeMount

    编译模板,调用 render 函数生成 vdom ,但还没有开始渲染 DOM

  • 创建并替换

    这一步把正在内存中渲染好的模板结构替换掉el属性指定的DOM元素

  • mounted

    渲染 DOM 完成,页面更新。组件创建完成,开始进入运行阶段。

    注意 mounted 不会保证所有的子组件也都被挂载完成。如果你希望等到整个视图都渲染完毕再执行某些操作,可以在 mounted 内部使用 vm.$nextTick

组件运行阶段

  • beforeUpdate

    data中的数据,已经是最新的数据了。但是,页面上渲染的数据,还是之前的旧数据

    在数据发生改变后,DOM 被更新之前被调用。这里适合在现有 DOM 将要被更新之前访问它,比如移除手动添加的事件监听器。

  • virtual DOM re-render and patch

    正在根据最新的data数据,重新渲染内存中的的DOM结构;并把渲染好的模板结构替换到页面上

  • updated

    在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。

    注意,尽量不要在 updated 中继续修改数据,否则可能会触发死循环。如果要相应状态改变,通常最好使用计算属性watcher 取而代之。

    注意,updated 不会保证所有的子组件也都被重新渲染完毕。如果你希望等到整个视图都渲染完毕,可以在 updated 里使用 vm.$nextTick

  • activated/onActivated

    keep-alive 缓存的组件激活时调用。

  • deactived/onDeactivated

    keep-alive 缓存的组件停用时调用。

组件销毁阶段

  • beforeDestory/beforeUnmount

    组件进入销毁阶段。组件即将被销毁,但是还没有真正开始销毁,此时组件还是正常可用的;data、methods等数据或方法,依旧可以被正常访问。

    卸载组件实例后调用,在这个阶段,实例仍然是完全正常的。

    移除、解绑一些全局事件、自定义事件,可以在此时操作。

  • 销毁过程

    销毁组件的数据侦听器,子组件,事件监听

  • destoryed/unmounted

    卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。

2.5.0+新增errorCaptured

  • 类型(err: Error, vm: Component, info: string) => ?boolean

  • 详细

    在捕获一个来自后代组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

当捕获一个来自子孙组件的错误时被调用

如何正确的操作 DOM

mountedupdated 都不会保证所有子组件都挂载完成,如果想等待所有视图都渲染完成,需要使用 $nextTick

mounted() {
  this.$nextTick(function () {
    // 仅在整个视图都被渲染之后才会运行的代码
  })
}

ajax 放在哪个生命周期合适?

一般有两个选择:createdmounted ,建议选择后者 mounted

执行速度

  • 从理论上来说,放在 created 确实会快一些
  • 但 ajax 是网络请求,其时间是主要的影响因素。从 createdmounted 是 JS 执行,速度非常快。
  • 所以,两者在执行速度上不会有肉眼可见的差距

代码的阅读和理解

  • 放在 created 却会带来一些沟通和理解成本,从代码的执行上来看,它会一边执行组件渲染,一边触发网络请求,并行
  • 放在 mounted 就是等待 DOM 渲染完成再执行网络请求,串行,好理解

所以,综合来看,更建议选择 mounted

Composition API 生命周期有何不同

  • setup 代替了 beforeCreatecreated
  • 生命周期换成了函数的形式,如 mounted -> onMounted 参考 https://v3.cn.vuejs.org/api/composition-api.html#%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E9%92%A9%E5%AD%90
import { onUpdated, onMounted } from 'vue'

export default {
    setup() {
        onMounted(() => {
            console.log('mounted')
        })
        onUpdated(() => {
            console.log('updated')
        })
    } 
}

computed 初始化在什么时候?

props,methods,data和computed,watch的初始化都是在beforeCreated和created之间完成的

watch,immediate为true触发时机

immediate如果为true, 也是发生在beforeCreate 之后,created之前

父子组件的生命周期顺序

加载渲染过程 :

  • 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

子组件更新过程:

  • 父beforeUpdate->子beforeUpdate->子updated->父updated

父组件更新过程:

  • 父beforeUpdate->父updated

销毁过程:

  • 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

父子组件及mixin的生命周期执行顺序:

  • mixin的beforeCreate > 父beforeCreate > mixin的created > 父created > mixin的beforeMount > 父beforeMount > 子beforeCreate > 子created > 子beforeMount > 子mounted > mixin的mounted >父mounted

image-20240308113947100

image-20240308113717011

<template>
  <div>
    <div ref="div">父组件</div>
    <input type="text" v-model="msg">
    <div>{{newMsg}}</div>
    <hr>
    <child :msg="msg"></child>
  </div>
</template>

<script>
import Child from './Child.vue'
export default {
  name: 'ParentDemo',
  components: {
    Child
  },
  data() {
    return {
      msg: '父组件的数据'
    }
  },
  beforeCreate() {
    console.log('父beforeCreate', this.msg)
  },
  created() {
    console.log('父created', this.msg)
  },
  beforeMount() {
    console.log('父beforeMount', this.$refs.div)
  },
  mounted() {
    console.log('父mounted', this.$refs.div)
  },
  beforeUpdate() {
    console.log('父beforeUpdate', this.$refs.div.innerHTML)
  },
  updated() {
    console.log('父updated', this.$refs.div.innerHTML)
  },
  activated() {
    console.log('父activated')
  },
  deactivated() {
    console.log('父deactivated')
  },
  beforeDestroy() {
    console.log('父beforeDestroy')
  },
  destroyed() {
    console.log('父destroyed')
  },
  errorCaptured() {
    console.log('父errorCaptured')
  },
  computed: {
    newMsg() {
      return this.msg + 'computed'
    }
  },
  watch: {
    msg: {
      handler(newVal, oldVal) {
        console.log('父watch', newVal, oldVal)
      },
      immediate: true
    }
  }
}
</script>

Child.vue

<template>
  <div>
    <div>子组件</div>
    <p>父组件传递过来的数据:{{msg}}</p>
  </div>
</template>

<script>
export default {
  name: 'ChildDemo',
  props: ['msg'],
  beforeCreate() {
    // 这里获取不到this.msg,获取会报错
    console.log('子beforeCreate')
  },
  created() {
    console.log('子created', this.msg)
  },
  beforeMount() {
    console.log('子beforeMount')
  },
  mounted() {
    console.log('子mounted')
  },
  beforeUpdate() {
    console.log('子beforeUpdate')
  },
  updated() {
    console.log('子updated')
  },
  activated() {
    console.log('子activated')
  },
  deactivated() {
    console.log('子deactivated')
  },
  beforeDestroy() {
    console.log('子beforeDestroy')
  },
  destroyed() {
    console.log('子destroyed')
  },
  errorCaptured() {
    console.log('子errorCaptured')
  }
}
</script>

上源码

beforeCreate->created之间做的事

  • inject
  • state (props,methods,computed,watched)
  • provide

vue/src/core/instance

image-20240308123420608

init.ts

image-20240308124238408

inject.ts

image-20240308124448616

state.ts

image-20240308124605288

inject.ts

image-20240308124708944

参考文章

  • 25
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱写bug的小邓程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值