一、生命周期概念
首先 生命周期是 一个vue实例从创建到销毁的过程,在这个过程中,他经历了从创建、初始化数据、编译模版、挂载Dom、渲染->更新->渲染、卸载等一系列过程。
这个过程包括了这几个生命周期函数,常见的分别有:
beforeCreate,created,
beforeMount,mounted,
beforeUpdate,updated,
beforeDestory,destoryed
不常见的是keep-alive缓存的组件激活或停用时会触发的actived和deactivated,还有捕获子孙组件报错的errorCaptured
二、详细过程
1、 new Vue() -> beforeCreate()
new Vue()实例化一个vue实例后,会先初始化这个实例的生命周期和事件相关属性。首先我先看看Vue实例的构造函数。
function Vue (options) {
this._init(options)
}
在init()函数进行初始化,并且在初始化过程中分别调用了3个初始化函数initLifecycle(),initEvents(),initRender()
- initLifecycle(): 初始化生命周期,在这个时候定义了一些属性比如表示当前生命状态的_isMounted ,_isDestroyed ,_isBeingDestroyed,表⽰keep-alive中组件状态的_inactive;以及parent,child等属性。
- initEvents(): 初始化事件相关属性,定义了once、off、emit、on几个函数。
- initRender():初始化 render 函数,定义了 createElement 函数。
2、执行beforeCreate()
实例初始化完成后,实例的props解析完成后,调用beforeCreate(),这时候的data,watch,computed,method等选项还未处理,不能访问到里面的方法和数据。一般情况下这个钩子不做业务逻辑,但可以增加混合,或者加个loading事件。
3、 beforeCreate() -> created()
beforeCreate()执行完后,会开始进行数据初始化、在这个过程中会定义data数据、方法、以及事件,并且完成数据劫持observe以及给组件实例配置watcher观察者实例(后续的数据发生变化时,才能感知到数据的变化并且完成页面的渲染)。
4、 created()
执⾏created⽣命周期函数时,已经可以拿到data下的数据以及methods下的⽅法了,所以在这⾥,我们可以开始调⽤⽅法进⾏数据请求了,但此时挂载阶段还没开始,$el属性仍不可用。 一般情况下,数据请求一般放在这里。因为created是在实例创建完毕后立即调用,这时候页面dom节点还未生成,而mounted的时候,dom节点已经生成了,请求到数据后再去修改属性,有可能导致页面闪动。所以数据请求一般放在created里。
5、created() -> beforeMount()
这里的过程就比较复杂:
- 首先先判断是否存在el选项,如果不存在则停止编译,直到调用vm.$mount(el)才会继续编译
- 在判断是否有template模板之前,其实还有一个判断---判断当前是否存在有render函数,如果有的话就直接使用当前的render函数,如果没有我们才开始检查是否有template模板
- 判断是否有template模板,如果有的话将template模板转换成render函数,如果没有template,那么我们就会直接将获取到的el(也就是我们常见的#app,#app⾥⾯可能还会有其他标签)编译成templae, 然后再将这个template转换成render函数。
关于el选项
el选项提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。可以是 CSS 选择器,也可以是一个 HTMLElement 实例。在实例挂载之后,元素可以用 vm.mount() 手动开启编译。
没有el选项会有怎么样的后果?
如果没有el选项则停止编译,也意味着暂时停止了生命周期,直到手动调用vm.$mount(el)函数时再判断是否指定template。
以下网友展示:
new Vue({
el: "#app",
beforeCreate: function () {
console.log("调用了beforeCreat钩子函数");
},
created: function () {
console.log("调用了created钩子函数");
},
beforeMount: function () {
console.log("调用了beforeMount钩子函数");
},
mounted: function () {
console.log("调用了mounted钩子函数");
},
});
正常情况有el选项打印如下,可以看到el选项有且正确时,生命周期正常执行。
// 没有el选项
new Vue({
beforeCreate: function () {
console.log("调用了beforeCreat钩子函数");
},
created: function () {
console.log("调用了created钩子函数");
},
beforeMount: function () {
console.log("调用了beforeMount钩子函数");
},
mounted: function () {
console.log("调用了mounted钩子函数");
},
});
当去掉el选项,我们可以看到生命周期钩子执行到created就结束了。
// 手动调用vm.$mount("#app");
var vm = new Vue({
beforeCreate: function () {
console.log("调用了beforeCreat钩子函数");
},
created: function () {
console.log("调用了created钩子函数");
},
beforeMount: function () {
console.log("调用了beforeMount钩子函数");
},
mounted: function () {
console.log("调用了mounted钩子函数");
},
});
vm.$mount("#app");
我们可以虽然没有el选项,但是我们通过vm.$mount("#app")手动添加也能顺利执行生命周期。
关于template
一个字符串模板作为Vue实例的标识使用。如果el存在,模板将会替换挂载的元素。挂载元素的内容都将被忽略,除非模板的内容有分发插槽。如果值以#开始,则它将被用作选择符,并使用匹配元素的 innerHTML作为模板(template: '#temp';会匹配id为temp的元素作为template)。 通俗来讲就是template选项就是一个dom字符串,类似于"<div>我是template的内容:小明今年{{age}}岁了</div>",然后这字符串会取缔挂载元素(<div id='#app'>)内容。
<head>
<meta charset="utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
我是el挂载的内容:小明今年{{age}}岁了
</div>
</body>
<script>
const vm= new Vue({
el:'#app',
data:{
age:17
},
template:'<div>我是template的内容:小明今年{{age}}岁了</div>'
})
</script>
template里面的模板会替换掉#app里面的内容,显示如下:
html结构如下:
关于render函数
new Vue({ render: h => h(App) })
作用就是将传入的模板编译成vnode,而代入到vue文件编译为原生html的主要流程如下,render函数就在第三步:
- 提取模板中的原生html和非原生html,以及属性,事件,指令等。
- 经过一些处理生成render函数。
- render函数将模板函数内容生成对应的vnode。
- 在经过patch过程diff得到要渲染到视图中的vnode。
- vue根据vnode创建真实的dom节点,也就是原生html插入到视图中,完成渲染。
6、beforeMount()
- beforeMount在组件被挂载之前调用。当这个钩子被调用时,组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程,开始渲染render函数这里一般不会增加业务逻辑
7、beforeMount() -> mounted()
* 先生成一个虚拟dom
* 然后通过diff算法新⽼虚拟dom对⽐计算,通过render函数将其渲染成真实dom,渲染出来的真实dom替换掉原来的vm.el,然后再将替换后的el append到我们的页⾯内。整个初步流程就算是⾛完了
8、mounted()
mounted在组件被挂载之后调用。此时可以通过DOM API获取到DOM节点,$ref属性可以访问。就是数据和dom都完成挂载。并且标识⽣命周期的⼀个属性_isMounted 置为true。所以mounted函数内,我们是可以操作dom的,因为这个时候dom已经渲染完成了。
9、beforeUpdate()
- beforeUpdate在组件即将因为一个响应式状态变更而更新其 DOM 树之前调用。简单来说就是对页面上的数据有影响的变化(注意是页面上的数据,要应用到视图才会变化),都会触发这个生命周期函数,但是在这里只更新了虚拟dom上的数据,还没更新到视图上。
注意 这里有个判断
- 判断当前的_isMounted是不是为ture并且_isDestroyed是不是为false,也就是说,保证dom已经被挂载的情况下,且当前组件并未被销毁,才会⾛update流程。
10、updated()
updated⾥⾯也可以操作dom,并拿到最新更新后的dom。不过这⾥我要插⼀句话了,mouted和updated的执⾏,并不会等待所有⼦组件都被挂载完成后再执⾏,所以如果你希望所有视图都更新完毕后再做些什么事情,那么你最好在mouted或者updated中加⼀个netTick()中去做。
11、beforeDestroy()
- 实例销毁之前调用。在这一步,实例仍然完全可用。该钩子在服务器端渲染期间不被调用。
12、destroyed()
- 实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
参考文献:
[1](【Vue】详解Vue生命周期 - 腾讯云开发者社区-腾讯云 (tencent.com))
[2](超详细vue生命周期解析(详解) - 百度文库 (baidu.com))
[3](保姆级vue生命周期 - 掘金 (juejin.cn))