钩子函数的特点
Vue 生命周期钩子函数是在组件生命周期中执行的特定函数。
这些钩子函数允许你在组件不同的生命周期阶段插入自定义的逻辑代码。
Vue 提供了一组预定义的生命周期钩子函数,每个钩子函数在组件生命周期的不同阶段被调用。
源码中如何注册、实现钩子函数?
代码示例
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进行封装。
const createHook = function(lifecycle) {
return function (hook, target = currentInstance) {
injectHook(lifecycle, hook, target)
}
}
柯里化的封装过程带来的优势
-
提高函数的复用性:可以将原本接收多个参数的函数转变为接收部分参数的函数。这样,我们可以复用具有相同生命周期的钩子函数的逻辑,只需传入不同的目标实例即可。这种封装方式使得代码更加灵活,易于扩展和维护。
-
简化函数调用:可以将原本需要在调用时传递的参数提前传入并固定,从而简化函数调用。在这个例子中,createHook 函数返回的函数只需要传递 hook 和可选的 target 参数即可,无需再重复传递 lifecycle 参数。
-
隐藏实现细节:可以隐藏一些实现细节,使得函数调用更加简洁,同时也减少了外部对内部函数的直接访问。这种封装方式可以提高代码的可维护性和安全性
injectHook方法源码解析
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钩子函数会在组件挂载之后执行。
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 钩子函数,保证它们按注册顺序依次执行。