12.8 注册生命周期
onMounted这类组合式 API 之所以知道「钩子注册给哪个组件」,靠的是一个全局的currentInstance:执行 setup 前设置它,执行完重置它。
一、问题:钩子注册给谁
组合式 API 的写法可以注册多个同类型钩子:
setup() {
onMounted(() => { console.log('mounted 1') })
onMounted(() => { console.log('mounted 2') })
}
但 onMounted(fn) 本身拿不到组件实例,因此需要一个「当前正在被初始化的组件实例」的全局引用。
二、currentInstance 与 onMounted
// 全局变量,存储当前正在被初始化的组件实例
let currentInstance = null
function setCurrentInstance(instance) {
currentInstance = instance
}
/**
* 将生命周期钩子函数添加到当前实例对象的 instance.mounted 数组中
* @param {Function} fn 生命周期钩子函数
*/
function onMounted(fn) {
if (currentInstance) {
currentInstance.mounted.push(fn)
} else {
// 没有实例 → 用户没在 setup 里调用
console.error('onMounted 函数只能在 setup 中调用')
}
}
三、实例准备 mounted 数组,并在 setup 前后设置 / 重置
const instance = {
state,
props: shallowReactive(props),
isMounted: false,
subTree: null,
slots,
// 存储通过 onMounted 注册的生命周期钩子函数
mounted: []
}
// 调用 setup 之前:设置当前组件实例
setCurrentInstance(instance)
const setupResult = setup && setup(shallowReadonly(instance.props), setupContext)
// setup 执行完毕后立即重置
setCurrentInstance(null)
重置为
null有两个作用:不影响后续其它组件的初始化;同时让「不在 setup 中调用onMounted」直接命中错误分支。
四、挂载完成后遍历执行
if (!instance.isMounted) {
patch(null, subTree, container, anchor)
instance.isMounted = true
// 选项式钩子
mounted && mounted.call(renderContext)
// 组合式钩子:遍历 instance.mounted 数组并逐个执行
instance.mounted && instance.mounted.forEach(hook => hook.call(renderContext))
}
// 测试
setup(props, { emit }) {
emit('change', 1, 2)
onMounted(() => { console.log('mounted 1') })
onMounted(() => { console.log('mounted 2') })
return { bar: 'hello setUp' }
}
多次调用
onMounted会往同一个数组里 push 多个函数,挂载后按注册顺序依次执行,输出mounted 1、mounted 2。
五、两种注册方式对比
| 方式 | 写法 | 存放位置 | 调用时机 |
|---|---|---|---|
| 选项式(见 12.3) | 选项对象上的 mounted() {} | 组件选项对象 | 首次挂载完成后 |
| 组合式(本节) | setup 中调用 onMounted(fn) | instance.mounted 数组 | 首次挂载完成后 |
除
mounted以外的组合式生命周期钩子原理相同:都是在对应时机遍历实例上存放钩子的数组。
相关
- 12.7 插槽的工作原理与实现:setupContext 的完整形态
- 12.3 组件实例与组件的生命周期:选项式生命周期钩子的调用时机