变与不变的生命周期
生命周期是每个Vue应用都需要关注的一个重要范畴。随着Vue框架从2.x版本升级到3.x版本,生命周期也进行了一些调整优化。本文将带您深入分析Vue2和Vue3在生命周期设计上的不同之处,以及其不变的内在运行机制。
vue2生命周期图示
vue2中8个生命周期函数:
- beforeCreate
在实例初始化之后,数据观测(data observer)和事件配置(event/watcher)之前被调用。
- created
在实例创建完成后被立即调用。在这一步,实例已完成以下配置:数据观测(data observer),属性和方法的运算,watch/event 事件回调。但是挂载阶段还没开始,$el 不存在。
- beforeMount
在实例创建完成后被立即调用。在这一步,实例已完成以下配置:数据观测(data observer),属性和方法的运算,watch/event 事件回调。但是挂载阶段还没开始,$el 不存在。
- mounted
el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。可以在这里操作 DOM。
- beforeUpdate
数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。这里适合在更新之前访问现有的 DOM。
- updated
由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
- beforeDestroy
实例销毁之前调用。在这一步,实例仍然完全可用。
- destroyed
Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
vue3 生命周期图示
vue3中10个生命周期函数:
- setup
创建设置阶段的初始化函数,早于组件实例的任何操作。
- onBeforeMount
挂载开始之前调用,可以访问到声明周期的数据。
- onMounted
挂载后立即调用,可以访问到DOM元素和进行额外操作。
- onBeforeUpdate
更新前调用,访问现有DOM。
- onUpdated
更新后调用,在 virtural DOM 打补丁后调用。
- onBeforeUnmount
销毁前调用,实例仍然可以访问。
- onUnmounted
销毁后调用,实例已完全解绑和清理。
- onErrorCaptured
子组件错误捕获时调用。
- onRenderTracked(新增)
它会在虚拟DOM打补丁前被调用,即在进行依赖追踪和重新渲染前。 这提供了一种机会来更高效地记录或响应何时渲染会被触发。
- onRenderTriggered(新增)
它会在虚拟DOM重新渲染后立即被调用。 这提供了一种方式来高效地执行类似动画的操作,因为它确保渲染正在进行中。
onRenderTracke、onRenderTriggered这两个钩子的主要作用是:
- 追踪何时重新渲染会被触发。
- 响应渲染何时发生,以进行高效操作。
例如:
- onRenderTracked可以追踪哪些依赖触发了渲染
- onRenderTriggered可以在渲染结束后做动画过渡
它们与Vue2中的生命周期不同,更侧重于渲染本身,为开发提供了更细粒度的控制能力。
这对一些需求重绘的组件很有帮助,如大数据表格或复杂动画场景。相比之下,Vue2缺乏这类精细控制渲染的能力。
vue3和vue2的生命周期对应关系:
- setup()对应created()
- setup()在beforeCreate()之前执行
- onMounted()对应mounted()
- onUpdated()对应updated()
- onUnmounted()对应destroyed()
- onBeforeUnmount()对应beforeDestroy()
- onRenderTracked()能跟踪更新的相应钩子为activated和deactivated
- onRenderTriggered()暂未在Vue2中
- onErrorCaptured()对应errorCaptured
- onServerPrefetch()对应serverPrefetch
vue3源码中如何实现
js
const onBeforeMount = createHook('bm' /* BEFORE_MOUNT */)
const onMounted = createHook('m' /* MOUNTED */)
const onBeforeUpdate = createHook('bu' /* BEFORE_UPDATE */)
const onUpdated = createHook('u' /* UPDATED */)
const onBeforeUnmount = createHook('bum' /* BEFORE_UNMOUNT */)
const onUnmounted = createHook('um' /* UNMOUNTED */)
const onRenderTriggered = createHook('rtg' /* RENDER_TRIGGERED */)
const onRenderTracked = createHook('rtc' /* RENDER_TRACKED */)
const onErrorCaptured = (hook, target = currentInstance) => {
injectHook('ec' /* ERROR_CAPTURED */, hook, target)
}
除去onErrorCaptured其他的钩子函数都是直接调用createHook。
createHook方法源码解析
createHook对injectHook进行封装。
js
const createHook = function(lifecycle) {
return function (hook, target = currentInstance) {
injectHook(lifecycle, hook, target)
}
}
injectHook方法源码解析
js
function injectHook(type, hook, target = currentInstance, prepend = false) {
// 从目标组件实例中获取相应的钩子函数数组
const hooks = target[type] || (target[type] = [])
// 封装传入的钩子函数,用于添加实例检查、错误处理等逻辑
const wrappedHook = hook.__weh ||
(hook.__weh = (...args) => {
// 检查目标组件是否已卸载
if (target.isUnmounted) {
return
}
// 停止依赖收集,避免执行钩子时产生副作用
pauseTracking()
// 设置当前运行组件实例为目标实例
setCurrentInstance(target)
// 调用传入的钩子函数
const res = callWithAsyncErrorHandling(hook, target, type, args)
// 重置当前组件实例
setCurrentInstance(null)
// 恢复依赖收集
resetTracking()
return res
})
// 根据传入的参数将封装后的钩子推入数组前或后
if (prepend) {
hooks.unshift(wrappedHook)
} else {
hooks.push(wrappedHook)
}
}
这样的设计非常易于理解,因为生命周期的钩子函数在组件的不同阶段执行,所以这些钩子函数需要保存在当前组件实例上。这样,后续就可以通过不同的字符串键查找对应的钩子函数数组,并执行相应的逻辑。
举例onBeforeMount 和 onMounted
onBeforeMount 注册的 beforeMount 钩子函数会在组件挂载之前执行,onMounted 注册的 mounted钩子函数会在组件挂载之后执行。
js
const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
// 创建响应式的副作用渲染函数
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
// 获取组件注册的钩子函数
const { bm, m } = instance;
// 渲染组件生成子树vnode
const subTree = (instance.subTree = renderComponentRoot(instance))
// 执行beforeMount钩子函数
if (bm) {
invokeArrayFns(bm)
}
// 将子树vnode挂载到container上
patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)
// 保留渲染生成的子树根DOM节点
initialVNode.el = subTree.el;
// 执行mounted钩子函数
if (m) {
queuePostRenderEffect(m, parentSuspense)
}
// 标记已挂载
instance.isMounted = true;
} else {
// 更新组件
}
}, prodEffectOptions)
}
在执行组件挂载之前,会检测组件实例上是否存在注册的 beforeMount 钩子函数(bm)。如果存在,通过遍历 instance.bm数组并使用 invokeArrayFns 方法依次执行这些钩子函数。这样设计的原因是用户可以通过多次调用 onBeforeMount函数来注册多个 beforeMount 钩子函数,保证它们按注册顺序依次执行。
小结:
- setup()早于任何其他钩子调用
- onMounted()在el被新创建的实例管理后调用
- onUpdated()在数据更新导致重新渲染后调用
- onUnmounted()实例移出DOM且事件解绑后调用
- onBeforeUnmount()实例即将被移除前调用
- onErrorCaptured()捕获子组件错误时调用
- onRenderTracked()/onRenderTriggered()跟渲染有关,Vue2无对应
- created()被setup()替代