开始
生命周期放在vue里面指的就是vue实例从创建到销毁的过程。
这个过程包括了这几个生命周期函数,常见的分别有: beforeCreate,created, beforeMount,mounted, beforeUpdate,updated, beforeDestory,destoryed
不常见的是keep-alive缓存的组件激活或停用时会触发的actived和deactivated,还有捕获子孙组件报错的errorCaptured。
下面着重讲常见的8个生命周期吧。
详细
1.new vue() ---> beforeCreate
首先new vue()表示我们要创造vue实例后,会先初始化这个实例的生命周期和事件相关属性。
从代码上看,Vue实例只是一个简单的构造函数,长这样:
function Vue (options) {
this._init(options)
}
在init()函数里面,初始化了一些属性:
- initLiftcycle:初始化一些属性例如
$parents
,$children
。 - initEvents:初始化事件相关属性如
_events
- initRender:初始化渲染相关。
2. beforeCreate
在实例初始化完成,实例的props解析完成后,会调用beforeCreate,此时的data,watch,computed,method等选项还未处理,不能访问到里面的方法和数据。
一般情况下这个钩子不做业务逻辑,但可以增加混合,或者加个loading事件。
3. beforeCreate ---> created
进行数据观测,初始化事件。
4. created
当数据观测完成,响应式数据、计算属性、方法和侦听器设置完成,data数据初始化,但此时挂载阶段还没开始,$el属性仍不可用。
一般情况下,数据请求一般放在这里。因为created是在实例创建完毕后立即调用,这时候页面dom节点还未生成,而mounted的时候,dom节点已经生成了,请求到数据后再去修改属性,有可能导致页面闪动。所以数据请求一般放在created里。
5. created ---> beforeMount
从created到beforeMount这里发生了什么一开始看别人的概括可能有些迷茫(就是我)。例如这样:
- 判断是否存在el选项,若不存在则停止编译,直到调用vm.$mount(el)才会继续编译
- 优先级:render > template > outerHTML
- vm.el获取到的是挂载DOM的
这一步的图是长这样的:
(1)判断el选项
乍一看,created后,el选项是什么?存不存在又有什么影响呢?
所以这里好好解释下:
简单来说el选项的作用就是表明我们要将当前vue组件生成的实例插入到页面的哪个元素中,就是经常提到的挂载元素
,它可以是css选择器,也可以是一个html实例。因为挂载元素内的东西都会被vue生成的dom代替,所以不建议挂载到html或者body上。
那有什么影响呢?
看图:
- 首先系统会判断有无el选项,如果有el选项,继续编译,到判断是否指定template。
- 如果没有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")手动添加也能顺利执行生命周期。
(2)判断template选项
template选项是什么?
一个字符串模板作为Vue实例的标识使用。如果el存在,模板将会替换挂载的元素。挂载元素的内容都将被忽略,除非模板的内容有分发插槽。如果值以#开始,则它将被用作选择符,并使用匹配元素的 innerHTML作为模板(template: '#temp';会匹配id为temp的元素作为template)。
通俗来讲就是template选项就是一个dom字符串,类似于'
像这样:
<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结构如下:
回到正题,在这一步,系统会判断是否有template模板:
- 如果Vue实例对象中有template参数选项,则将其作为模板编译成render函数。
- 如果没有template参数选项,则将外部的HTML作为模板编译(template),也就是说,template参数选项的优先级要比外部的HTML高。
这里的render函数是什么?外部HTML指的又是什么?
先简单认识下render函数,就长这样:
new Vue({ render: h => h(App) })
作用就是将传入的模板编译成vnode,而代入到vue文件编译为原生html的主要流程如下,render函数就在第三步:
- 提取模板中的原生html和非原生html,以及属性,事件,指令等。
- 经过一些处理生成render函数。
- render函数将模板函数内容生成对应的vnode。
- 在经过patch过程diff得到要渲染到视图中的vnode。
- vue根据vnode创建真实的dom节点,也就是原生html插入到视图中,完成渲染。
而外部HTML实际上指的就是通过el属性获取到的挂载元素,如果纠结outerhtml(外部HTML)是什么的话可以看下解释:
innerHTML:得出调用该方法的节点下的HTML代码,但不包括该节点本身的HTML代码(没我有子) outerHTML:得出调用该方法的节点及该节点下的HTML代码(有我有子)
所以这一步通俗来说就是
有声明template就抓template进render: h => h(App)中,没有声明template就直接抓el进render。
(3)其实还得先判断render
如果光看图的话,上面判断el和判断template已经讲清楚了(不清楚可以去翻下下面的参考)。
但是这里还有一个点需要注意,前面提到过一个优先级:render > template > outerHTML,这是因为如果Vue选项中的render函数存在,则Vue不会从template选项或者通过el指定的挂载元素中提取html模板来编译渲染函数。
例子:
// template render同时存在
<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:22
},
template:'<div>我是template的内容:小明今年{{age}}岁了</div>',
render(h){
return h('div',`我是render的内容:小明今年${this.age}岁了`)
}
})
</script>
结果:
所以这里可以来个小总结:
判断el --> 判断render --> 判断template
- 首先判断el选项是否存在,不存在则等到手动调用vm.$mount(el)再继续生命周期
- 然后判断是否有render渲染函数
- 有render函数,直接使用render渲染函数生成vnode
- 没有render函数,则判断是否有template选项
- 有template选项,将template选项放到render函数生成vnode
- 没有template选项,将通过el获取的元素放到render函数生成vnode
- 都没有那就报错。
6. beforeMount
beforeMount在组件被挂载之前调用。当这个钩子被调用时,组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程。这里一般不会增加业务逻辑。
7. beforeMount ---> mounted
上面说到还没有创建dom节点,而这一步就是用内存中编译好的html结构替换掉el指定的元素的内容,就是前面提到的vue文件编译为原生html的主要流程的4,5步。这时数据初始化完成,但是数据的双向绑定还是{{}},这是因为vue采用了虚拟dom技术。
8. mounted
mounted在组件被挂载之后调用。此时可以通过DOM API获取到DOM节点,$ref属性可以访问。就是数据和dom都完成挂载。
如何界定是否已挂载,官网:
组件在以下情况下被视为已挂载:所有同步子组件都已经被挂载。(不包含异步组件或 <Suspense> 树内的组件)其自身的 DOM 树已经创建完成并插入了父容器中。注意仅当根容器在文档中时,才可以保证组件 DOM 树也在文档中。
9. beforeUpdate
beforeUpdate在组件即将因为一个响应式状态变更而更新其 DOM 树之前调用。简单来说就是对页面上的数据有影响的变化(注意是页面上的数据,要应用到视图才会变化),都会触发这个生命周期函数,但是在这里只更新了虚拟dom上的数据,还没更新到视图上。
注意:vue 更新方式是组件级别的,比如我们项目会有许多组件,在根组件中引用3个组件,其中一个组件更新重新渲染了(异步渲染),另外两个不会重新渲染。而且可以在这个钩子函数中,增加一些数据更新,不会导致试图多次更新。
10. beforeUpdate ---> updated
diff 更新视图。
11. updated
updated在组件因为一个响应式状态变更而更新其 DOM 树之后调用。这里虚拟 DOM 重新渲染和打补丁之后调用,组件DOM已经更新,可执行依赖于DOM的操作。
注意:这个钩子函数就不要再去更新数据,可能会发生死循环。
这里突然想到this.nextTick与updated执行顺序,详细的可以看参考,结论是父的beforeUpdate->子的beforeUpdate->子的updated->父updated->nextTick回调。
12. beforeDestroy
beforeDestroy钩子函数在实例销毁之前调用。在这一步,实例仍然完全可用。一般做事件的移除、清空定时器。
13. destory
destroyed钩子函数在Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
参考
cloud.tencent.com/developer/a… 【Vue】详解Vue生命周期 juejin.cn/post/684490… 【干货】详解vue生命周期 cn.vuejs.org/api/options… 官方文档 www.cnblogs.com/vickylinj/p… 【Vue】说说你对DOM选项el、template、render的理解?
juejin.cn/post/701129… render 函数是怎么来的?深入浅出 Vue 中的模板编译 segmentfault.com/a/119000004… Vue中的nextTick和update生命周期