生命周期钩子实现
Vue 是如何在正确的时机调用正确的函数?
生命周期概览
beforeCreate ← 实例刚创建,data/methods 还未初始化
↓
created ← 实例创建完成,data/methods 已可用
↓
beforeMount ← 挂载前,DOM 还未渲染
↓
mounted ← 挂载完成,DOM 已渲染
↓
beforeUpdate ← 更新前,DOM 未重新渲染
↓
updated ← 更新完成,DOM 已重新渲染
↓
beforeUnmount ← 卸载前,DOM 还未移除
↓
unmounted ← 卸载完成,DOM 已移除1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
生命周期钩子的存储
每个组件实例都有自己的生命周期钩子列表:
javascript
/**
* 生命周期钩子类型
* @typedef {'bm'|'bC'|'c'|'bmnt'|'mnt'|'bu'|'u'|'bum'|'um'} LifecycleHook
*/
// 简化的生命周期 ID
const lifycycleId = {
bm: 'beforeMount', // beforeMount
bC: 'beforeCreate', // beforeCreate
c: 'created', // created
bmnt: 'beforeMount', // beforeMount (别名)
mnt: 'mounted', // mounted
bu: 'beforeUpdate', // beforeUpdate
u: 'updated', // updated
bum: 'beforeUnmount', // beforeUnmount
um: 'unmounted' // unmounted
}
/**
* 组件实例的完整结构(简化版)
*/
const instance = {
// 生命周期钩子
bm: [], // beforeMount
bC: [], // beforeCreate
c: [], // created
bmnt: [], // beforeMount (另一个 key)
mnt: [], // mounted
bu: [], // beforeUpdate
u: [], // updated
bum: [], // beforeUnmount
um: [], // unmounted
// 其他...
}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
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
注册生命周期钩子
Vue 提供 onXxx 函数来注册生命周期钩子:
javascript
/**
* 全局变量:当前正在设置的组件实例
*/
let currentInstance = null
/**
* 设置当前实例
*/
function setCurrentInstance(instance) {
currentInstance = instance
}
/**
* 获取当前实例
*/
function getCurrentInstance() {
return currentInstance
}
/**
* 注册生命周期钩子
* @param {LifecycleHook} hookName - 钩子名称简写
* @param {Function} hook - 回调函数
*/
function onMounted(hook) {
// 获取当前实例
const instance = getCurrentInstance()
if (instance) {
// 注册到 mounted 钩子列表
instance.mnt.push(hook)
} else {
// 在实例外部调用 onMounted 会警告
console.warn('onMounted() 只能在 setup() 或生命周期钩子中使用')
}
}
// 类似的函数...
const onBeforeMount = createHook('bm')
const onBeforeUpdate = createHook('bu')
const onBeforeUnmount = createHook('bum')
const onUpdated = createHook('u')
const onUnmounted = createHook('um')
const onCreated = createHook('c')
/**
* 创建生命周期钩子注册函数
* @param {LifecycleHook} type - 钩子类型简写
*/
function createHook(type) {
return function(hook) {
const instance = getCurrentInstance()
if (instance) {
instance[type].push(hook)
}
}
}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
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
执行生命周期钩子
javascript
/**
* 触发生命周期钩子
* @param {ComponentInstance} instance - 组件实例
* @param {LifecycleHook} type - 钩子类型
* @param {*} args - 传递给钩子的参数
*/
function invokeArrayFns(fns, args) {
// 遍历并执行所有钩子函数
for (let i = 0; i < fns.length; i++) {
fns[i](args)
}
}
/**
* 执行指定类型的所有钩子
*/
function invokeLifecycleHook(instance, type, args) {
const hooks = instance[type]
if (hooks && hooks.length) {
hooks.forEach(hook => hook(args))
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
组件挂载流程
javascript
/**
* 挂载组件到 DOM
* @param {ComponentInstance} instance - 组件实例
* @param {Element} container - 挂载容器
*/
function mountComponent(instance, container) {
// 1. 调用 beforeMount 钩子
invokeLifecycleHook(instance, 'bm')
// 2. 渲染组件,获取 vnode
const vnode = instance.render()
// 3. 创建 DOM 元素并渲染
const el = createElement(vnode)
// 4. 保存 el 引用
instance.el = el
// 5. 插入到容器
container.appendChild(el)
// 6. 标记为已挂载
instance.isMounted = true
// 7. 调用 mounted 钩子
invokeLifecycleHook(instance, 'mnt')
// 8. 注册 afterMount effect(响应式数据变化时更新)
setupRenderEffect(instance, container)
}
/**
* 设置渲染 effect
* 当响应式数据变化时,自动重新渲染
*/
function setupRenderEffect(instance, container) {
// 创建一个 effect,监听所有响应式依赖
effect(() => {
// 重新渲染
const vnode = instance.render()
// 更新 DOM
patch(instance.el, vnode)
// 调用 beforeUpdate 钩子
invokeLifecycleHook(instance, 'bu')
// DOM 更新完成
// 调用 updated 钩子
invokeLifecycleHook(instance, 'u')
})
// 将 effect 保存到实例
instance.updateEffect = currentEffect
}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
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
组件更新流程
javascript
/**
* 更新组件
* 当依赖的数据变化时自动调用
*/
function updateComponent(instance) {
// 1. 调用 beforeUpdate 钩子
invokeLifecycleHook(instance, 'bu')
// 2. 重新渲染
const vnode = instance.render()
// 3. 触发 DOM 更新(diff 算法)
patch(instance.el, vnode)
// 4. 调用 updated 钩子
invokeLifecycleHook(instance, 'u')
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
组件卸载流程
javascript
/**
* 卸载组件
* @param {ComponentInstance} instance - 组件实例
*/
function unmountComponent(instance) {
// 1. 调用 beforeUnmount 钩子
invokeLifecycleHook(instance, 'bum')
// 2. 移除 DOM
if (instance.el) {
instance.el.parentNode.removeChild(instance.el)
}
// 3. 清理 effect
if (instance.updateEffect) {
instance.updateEffect.active = false
}
// 4. 标记为已卸载
instance.isUnmounted = true
// 5. 调用 unmounted 钩子
invokeLifecycleHook(instance, 'um')
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
完整的生命周期流程
javascript
/**
* 完整的组件挂载示例
*/
function mountComponent(instance, container) {
// 阶段 1: 创建阶段
// beforeCreate - 实例刚创建
invokeLifecycleHook(instance, 'bC')
// setup() 执行(在 createComponentInstance 中已调用)
// created - 实例创建完成,data/methods 可用
invokeLifecycleHook(instance, 'c')
// 阶段 2: 挂载阶段
// beforeMount - 挂载前
invokeLifecycleHook(instance, 'bm')
// 创建 DOM
const el = instance.el = createElement(instance.render())
// 插入 DOM
container.appendChild(el)
// mounted - 挂载完成
instance.isMounted = true
invokeLifecycleHook(instance, 'mnt')
// 设置更新 effect(响应式数据变化时自动更新)
instance.updateEffect = effect(() => {
// beforeUpdate - 更新前
invokeLifecycleHook(instance, 'bu')
// 重新渲染
patch(instance.el, instance.render())
// updated - 更新完成
invokeLifecycleHook(instance, 'u')
})
}
/**
* 组件更新示例
*/
function updateComponent(instance, newProps) {
// 更新 props
instance.props = newProps
// 触发 updateEffect 重新执行
if (instance.updateEffect) {
instance.updateEffect.fn()
}
}
/**
* 组件卸载示例
*/
function unmountComponent(instance) {
// beforeUnmount - 卸载前
invokeLifecycleHook(instance, 'bum')
// 移除 DOM
instance.el.parentNode.removeChild(instance.el)
// 停止响应式追踪
instance.updateEffect.active = false
// unmounted - 卸载完成
instance.isUnmounted = true
invokeLifecycleHook(instance, 'um')
}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
58
59
60
61
62
63
64
65
66
67
68
69
70
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
58
59
60
61
62
63
64
65
66
67
68
69
70
实际使用示例
javascript
import { ref, onMounted, onUpdated, onBeforeUnmount } from 'vue'
const MyComponent = {
setup() {
const count = ref(0)
// mounted 钩子
onMounted(() => {
console.log('组件已挂载')
// 可以访问 DOM
console.log('el:', instance.el)
})
// updated 钩子
onUpdated(() => {
console.log('组件已更新')
})
// beforeUnmount 钩子
onBeforeUnmount(() => {
console.log('组件即将卸载')
// 清理资源,如定时器、事件监听等
clearInterval(timer)
})
// 定时器示例
const timer = setInterval(() => {
count.value++
}, 1000)
// 清理函数(组件卸载时自动调用)
return () => ({
type: 'div',
children: [`计数: ${count.value}`]
})
}
}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
父子组件生命周期执行顺序
当父子组件同时挂载时:
父 beforeCreate
父 created
父 beforeMount
子 beforeCreate
子 created
子 beforeMount
子 mounted ← 子组件先完成
父 mounted ← 父组件最后完成1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
当父组件卸载时:
父 beforeUnmount
子 beforeUnmount
子 unmounted ← 子组件先卸载
父 unmounted ← 父组件最后卸载1
2
3
4
2
3
4
总结
| 钩子 | 调用时机 | 用途 |
|---|---|---|
| beforeCreate | 实例创建后,data/methods 初始化前 | 初始化非响应式状态 |
| created | data/methods 初始化后 | 发起 API 请求、注册事件 |
| beforeMount | DOM 渲染前 | 最后准备 |
| mounted | DOM 挂载后 | 操作 DOM、启动定时器 |
| beforeUpdate | DOM 更新前 | 获取更新前的状态 |
| updated | DOM 更新后 | 操作更新后的 DOM |
| beforeUnmount | 卸载前 | 清理资源、取消订阅 |
| unmounted | 卸载后 | 完全清理 |
核心原理:通过 invokeLifecycleHook 函数,在组件生命周期的不同时机,遍历执行对应的钩子函数数组。