vue 生命周期深入
这篇博客将会从下面四个常见的应用诠释组件的生命周期, 以及各个生命周期应该干什么事
- 单组件的生命周期
- 父子组件的生命周期
- 兄弟组件的生命周期
- 宏 mixin 的生命周期
生命周期: Vue 实例从开始创建、初始化数据、编译模板、挂载 Dom→ 渲染、更新 → 渲染、卸载等一系列过程, 我们称这是 Vue 的生命周期, 各个阶段有相对应的事件钩子
# 1. 生命周期钩子函数
下面这张图是 vue 生命周期各个阶段的执行情况:
生命周期钩子函数 | 组件状态 | 最佳实践 |
---|---|---|
beforeCreate | 实例初始化之后, this 指向创建的实例, 不能访问到 data、computed、watch、methods 上的方法和数据 | 常用于初始化非响应式变量 |
created | 实例创建完成, 可访问 data、computed、watch、methods 上的方法和数据, 未挂载到 DOM, 不能访问到 $el 属性, $ref 属性内容为空数组 | 常用于简单的 ajax 请求, 页面的初始化 |
beforeMount | 在挂载开始之前被调用, beforeMount 之前, 会找到对应的 template, 并编译成 render 函数 | - |
mounted | 实例挂载到 DOM 上, 此时可以通过 DOM API 获取到 DOM 节点, $ref 属性可以访问 | 常用于获取 VNode 信息和操作, ajax 请求 |
beforeupdate | 响应式数据更新时调用, 发生在虚拟 DOM 打补丁之前 | 适合在更新之前访问现有的 DOM, 比如手动移除已添加的事件监听器 |
updated | 虚拟 DOM 重新渲染和打补丁之后调用, 组件 DOM 已经更新, 可执行依赖于 DOM 的操作 | 避免在这个钩子函数中操作数据, 可能陷入死循环 |
beforeDestroy | 实例销毁之前调用. 这一步, 实例仍然完全可用, this 仍能获取到实例 | 常用于销毁定时器、解绑全局事件、销毁插件对象等操作 |
destroyed | 实例销毁后调用, 调用后, Vue 实例指示的所有东西都会解绑定, 所有的事件监听器会被移除, 所有的子实例也会被销毁 | - |
注意:
- created 阶段的 ajax 请求与 mounted 请求的区别:前者页面视图未出现,如果请求信息过多,页面会长时间处于白屏状态
mounted
不会承诺所有的子组件也都一起被挂载. 如果你希望等到整个视图都渲染完毕, 可以用 vm.$nextTick (opens new window)- vue2.0 之后主动调用 $destroy() 不会移除 dom 节点,作者不推荐直接 destroy 这种做法,如果实在需要这样用可以在这个生命周期钩子中手动移除 dom 节点
# 2. 单个组件的生命周期
现根据实际代码执行情况分析:
<template>
<div>
<h3>单组件</h3>
<el-button @click="dataVar += 1"> 更新 {{dataVar}}</el-button>
<el-button @click="handleDestroy"> 销毁 </el-button>
</div>
</template>
1
2
3
4
5
6
7
2
3
4
5
6
7
export default {
data() {
return {
dataVar: 1
}
},
beforeCreate() {
this.compName = 'single'
console.log(`--${this.compName}--beforeCreate`)
},
created() {
console.log(`--${this.compName}--created`)
},
beforeMount() {
console.log(`--${this.compName}--beforeMount`)
},
mounted() {
console.log(`--${this.compName}--mounted`)
},
beforeUpdate() {
console.log(`--${this.compName}--beforeUpdate`)
},
updated() {
console.log(`--${this.compName}--updated`)
},
beforeDestroy() {
console.log(`--${this.compName}--beforeDestroy`)
},
destroyed() {
console.log(`--${this.compName}--destroyed`)
},
methods: {
handleDestroy() {
this.$destroy()
}
}
}
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
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
初始化组件时, 打印:
当 data 中的值变化时, 打印:
当组件销毁时, 打印:
从打印结果可以看出:
- 初始化组件时,仅执行了 beforeCreate/Created/beforeMount/mounted 四个钩子函数
- 当改变 data 中定义的变量(响应式变量)时,会执行 beforeUpdate/updated 钩子函数
- 当切换组件(当前组件未缓存)时,会执行 beforeDestory/destroyed 钩子函数
- 初始化和销毁时的生命钩子函数均只会执行一次,beforeUpdate/updated 可多次执行
# 3. 父子组件的生命周期
将单组件作为基础组件(由于 props 在 beforeCreate() 中未初始化), 需要做如下更改:
props: {
compName: {
type: String,
default: 'single'
}
},
beforeCreate() {
// this.compName = 'single'
// console.log(`--${this.compName}--beforeCreate`)
console.log(` --data 未初始化 --beforeCreate`)
},
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
父组件代码如下:
<template>
<div class="complex">
<h3>复杂组件</h3>
<lifecycle-single compName="child"></lifecycle-single>
</div>
</template>
1
2
3
4
5
6
2
3
4
5
6
const COMPONENT_NAME = 'complex'
import LifecycleSingle from './LifeCycleSingle'
export default {
beforeCreate() {
console.log(`--${COMPONENT_NAME}--beforeCreate`)
},
created() {
console.log(`--${COMPONENT_NAME}--created`)
},
beforeMount() {
console.log(`--${COMPONENT_NAME}--beforeMount`)
},
mounted() {
console.log(`--${COMPONENT_NAME}--mounted`)
},
beforeUpdate() {
console.log(`--${COMPONENT_NAME}--beforeUpdate`)
},
updated() {
console.log(`--${COMPONENT_NAME}--updated`)
},
beforeDestroy() {
console.log(`--${COMPONENT_NAME}--beforeDestroy`)
},
destroyed() {
console.log(`--${COMPONENT_NAME}--destroyed`)
},
components: {
LifecycleSingle
}
}
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
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
初始化组件时, 打印:
当子组件 data 中的值变化时, 打印:
当父组件 data 中的值变化时, 打印:
当 props 改变时, 打印:
当子组件销毁时, 打印:
当父组件销毁时, 打印:
从打印结果可以看出:
- 仅当子组件完成挂载后,父组件才会挂载
- 当子组件完成挂载后,父组件会主动执行一次 beforeUpdate/updated 钩子函数(仅首次)
- 父子组件在 data 变化中是分别监控的,但是在更新 props 中的数据是关联的(可实践)
- 销毁父组件时,先将子组件销毁后才会销毁父组件
# 4. 兄弟组件的生命周期
在上面的基础上, 复杂组件做如下更改
<template>
<div class="complex">
<h3>复杂组件</h3>
<lifecycle-single compName="cihld1"></lifecycle-single>
<lifecycle-single compName="child2"></lifecycle-single>
<el-button @click="dataVar += 1">complex 更新 {{dataVar}}</el-button>
<el-button @click="handleDestroy">complex 销毁 </el-button>
</div>
</template>
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
初始化组件时, 打印:
当 child1 更新和销毁时, 打印:
当 child2 更新和销毁时, 打印:
当父组件销毁时, 打印:
从打印结果可以看出:
- 组件的初始化(mounted 之前)分开进行,挂载是从上到下依次进行
- 当没有数据关联时,兄弟组件之间的更新和销毁是互不关联的
# 5. 宏 mixin 的生命周期
在上面的基础上, 添加一个 mixin.js 文件, 内容如下:
const COMPONENT_NAME = 'lifecycleMixin'
export default {
name: COMPONENT_NAME,
beforeCreate() {
console.log(`--${COMPONENT_NAME}--beforeCreate`)
},
created() {
console.log(`--${COMPONENT_NAME}--created`)
},
beforeMount() {
console.log(`--${COMPONENT_NAME}--beforeMount`)
},
mounted() {
console.log(`--${COMPONENT_NAME}--mounted`)
},
beforeUpdate() {
console.log(`--${COMPONENT_NAME}--beforeUpdate`)
},
updated() {
console.log(`--${COMPONENT_NAME}--updated`)
},
beforeDestroy() {
console.log(`--${COMPONENT_NAME}--beforeDestroy`)
},
destroyed() {
console.log(`--${COMPONENT_NAME}--destroyed`)
}
}
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
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
同样的, 复杂组件做如下更改:
import lifecycleMixin from './mixin'
export default {
mixins: [lifecycleMixin]
// ...
}
1
2
3
4
5
6
2
3
4
5
6
组件初始化时, 打印:
组件销毁时, 打印:
从打印结果可以看出:
mixin 中的生命周期与引入该组件的生命周期是仅仅关联的, 且 mixin 的生命周期优先执行
在此仅将 demo 中的核心部分列出, 完整的代码请查看 demo 源码 (opens new window)
# 参考资料:
编辑 (opens new window)
上次更新: 12/31/2020, 7:24:06 AM
- 01
- 搭配 Jenkins 实现自动化打包微前端多个项目09-15
- 02
- 自动化打包微前端多个项目09-15
- 03
- el-upload 直传阿里 oss 并且显示自带进度条和视频回显封面图06-05