目录
我们在学习Vue的时候,都知道Vue是有生命周期beforeCreate,created,beforeMounted等等,以及从源码学习当中经常看到类似代码,根据注释才知道是生命周期钩子,那么生命周期如何注册我们还不知道
// beforeMount hook
if (bm) {
invokeArrayFns(bm)
}
// invokeArrayFns 方法
export const invokeArrayFns = (fns: Function[], arg?: any) => {
for (let i = 0; i < fns.length; i++) {
fns[i](arg)
}
}
1. LifecycleHooks (enum 种类)
根据源码,不难发现bm只是这些钩子的简称
export const enum LifecycleHooks {
BEFORE_CREATE = 'bc',
CREATED = 'c',
BEFORE_MOUNT = 'bm',
MOUNTED = 'm',
BEFORE_UPDATE = 'bu',
UPDATED = 'u',
BEFORE_UNMOUNT = 'bum',
UNMOUNTED = 'um',
DEACTIVATED = 'da',
ACTIVATED = 'a',
RENDER_TRIGGERED = 'rtg',
RENDER_TRACKED = 'rtc',
ERROR_CAPTURED = 'ec',
SERVER_PREFETCH = 'sp'
}
2. 生命周期函数
我们在setup中使用的这些函数,注册生命周期钩子,都使用了createHook方法,那么createHook方法都干了什么呢
//生命周期钩子
export const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT)
export const onMounted = createHook(LifecycleHooks.MOUNTED)
export const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE)
export const onUpdated = createHook(LifecycleHooks.UPDATED)
export const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT)
export const onUnmounted = createHook(LifecycleHooks.UNMOUNTED)
export const onServerPrefetch = createHook(LifecycleHooks.SERVER_PREFETCH)
3. createHook函数
createHook函数主要只做了使用injectHook函数,但是因为采用了简写方式看起来较为复杂,下面有化简前的大概样子,当你使用onMounted(()=>{})函数,内部的函数其实就是hook参数
// 源码createHook
export const createHook =
<T extends Function = () => any>(lifecycle: LifecycleHooks) =>
(hook: T, target: ComponentInternalInstance | null = currentInstance) =>
// post-create lifecycle registrations are noops during SSR (except for serverPrefetch)
(!isInSSRComponentSetup || lifecycle === LifecycleHooks.SERVER_PREFETCH) &&
injectHook(lifecycle, (...args: unknown[]) => hook(...args), target)
// 化简前
export const createHook = (lifecycle) => {
return (hook, target = currentInstance) => {
return injectHook(lifecycle, hook, target);
};
};
4. injectHook函数
injectHook是一个闭包函数,通过闭包缓存绑定对应生命周期Hooks到对应的组件实例上,并且是一个数组,因为可能你会多次调用同一个组件的同一个生命周期函数,此时当你调用组件实例,instance.bm其实就是访问这个Hooks数组
最后injectHook会返回一个wrappedHook,这个函数内部其实就是对injectHook函数中的hook进行使用---也就是OnMounted 内部的函数进行了调用
export function injectHook(
type: LifecycleHooks,
hook: Function & { __weh?: Function },
target: ComponentInternalInstance | null = currentInstance,
prepend: boolean = false
): Function | undefined {
if (target) {
//把各个生命周期的hook函数挂载到组件实例上
//并且是一个数组,因为可能你会多次调用同一个组件的同一个生命周期函数
//example--在setup函数中调用多个onMounted
// onMounted(() => {
// console.log('hello1');
// })
// onMounted(() => {
// console.log('hello2');
// })
const hooks = target[type] || (target[type] = [])
// cache the error handling wrapper for injected hooks so the same hook
// can be properly deduped by the scheduler. "__weh" stands for "with error
// handling".
//把生命周期函数进行包装并且把包装函数缓存在__weh上
const wrappedHook =
hook.__weh ||
(hook.__weh = (...args: unknown[]) => {
if (target.isUnmounted) {
return
}
// disable tracking inside all lifecycle hooks
// since they can potentially be called inside effects.
pauseTracking()
// Set currentInstance during hook invocation.
// This assumes the hook does not synchronously trigger other hooks, which
// can only be false when the user does something really funky.
setCurrentInstance(target)
//执行生命周期Hooks函数
const res = callWithAsyncErrorHandling(hook, target, type, args)
unsetCurrentInstance()
resetTracking()
return res
})
if (prepend) {
hooks.unshift(wrappedHook)
} else {
// 把生命周期的包装函数绑定到组件实例对应的hooks上
hooks.push(wrappedHook)
}
//返回包装函数
return wrappedHook
} else if (__DEV__) {
const apiName = toHandlerKey(ErrorTypeStrings[type].replace(/ hook$/, ''))
warn(
`${apiName} is called when there is no active component instance to be ` +
`associated with. ` +
`Lifecycle injection APIs can only be used during execution of setup().` +
(__FEATURE_SUSPENSE__
? ` If you are using async setup(), make sure to register lifecycle ` +
`hooks before the first await statement.`
: ``)
)
}
}
5. invokeArrayFns
上面已经介绍完如何注册钩子函数,接下来就是如何使用钩子函数
下面就是compoentUpdteFn函数,不难发现他们都是先检查有无bm,m等,然后触发invokeArrayFns,该函数就是调用之前注册的hooks数组,hooks数组内部存储的就是我们注册hook函数的使用,因此就触发了钩子函数------(部分钩子函数将会涉及到scheduler)
因此我们可以清晰的知道
所以在初始化创建的时候,是深度递归创建子组件的过程,父子组件的生命周期的执行顺序是:
并且mounted实际是等组件挂载完执行(因为涉及到scheduler队列)
- 父组件 -> beforeMount
- 子组件 -> beforeMount
- 子组件 -> mounted
- 父组件 -> mounted
父子组件更新顺序同样是深度递归执行的过程:
- 如果父子组件没通过props传递数据,那么更新的时候,就各自执行各自的更新生命周期函数。
- 如果父子组件存在通过props传递数据的话,就必须先更新父组件,才能更新子组件。因为父组件 DOM 更新前,需要修改子组件的 props,子组件的 props 才是正确的值。---(next通过updateCompoentFn结合看)
// invokeArrayFns 方法
export const invokeArrayFns = (fns: Function[], arg?: any) => {
for (let i = 0; i < fns.length; i++) {
fns[i](arg)
}
}
const componentUpdateFn = () => {
if (!instance.isMounted) {
// 此时activeEffect为当前的effect
// 组件初始化的时候会执行这里
// 为什么要在这里调用 render 函数呢
// 是因为在 effect 内调用 render 才能触发依赖收集
// 等到后面响应式的值变更后会再次触发这个函数
let vnodeHook: VNodeHook | null | undefined
const { el, props } = initialVNode
const { bm, m, parent } = instance
const isAsyncWrapperVNode = isAsyncWrapper(initialVNode)
toggleRecurse(instance, false)
// beforeMount hook
if (bm) {
invokeArrayFns(bm)
}
// onVnodeBeforeMount
if (
!isAsyncWrapperVNode &&
(vnodeHook = props && props.onVnodeBeforeMount)
) {
invokeVNodeHook(vnodeHook, parent, initialVNode)
}
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
instance.emit('hook:beforeMount')
}
toggleRecurse(instance, true)
if (el && hydrateNode) {
// vnode has adopted host node - perform hydration instead of mount.
const hydrateSubTree = () => {
if (__DEV__) {
startMeasure(instance, `render`)
}
//根渲染节点赋值到subTree---里面执行render函数(进行依赖的收集)
instance.subTree = renderComponentRoot(instance)
if (__DEV__) {
endMeasure(instance, `render`)
}
if (__DEV__) {
startMeasure(instance, `hydrate`)
}
hydrateNode!(
el as Node,
instance.subTree,
instance,
parentSuspense,
null
)
if (__DEV__) {
endMeasure(instance, `hydrate`)
}
}
if (isAsyncWrapperVNode) {
; (initialVNode.type as ComponentOptions).__asyncLoader!().then(
// note: we are moving the render call into an async callback,
// which means it won't track dependencies - but it's ok because
// a server-rendered async wrapper is already in resolved state
// and it will never need to change.
() => !instance.isUnmounted && hydrateSubTree()
)
} else {
hydrateSubTree()
}
} else {
if (__DEV__) {
startMeasure(instance, `render`)
}
//根渲染节点赋值到subTree---里面执行render函数
//1.执行render函数,创建虚拟子节点
//2.进行数据的依赖收集(setup中的数据进行了proxy代理)
//3.在render中通过this访问instance.proxy,instance.proxy对数据访问进行不同proxy处理
const subTree = (instance.subTree = renderComponentRoot(instance))
if (__DEV__) {
endMeasure(instance, `render`)
}
if (__DEV__) {
startMeasure(instance, `patch`)
}
// 基于 subTree 再次调用 patch
// 基于 render 返回的 vnode ,再次进行渲染
// 递归的开箱(对于render的虚拟节点进行patch)
patch(
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
)
if (__DEV__) {
endMeasure(instance, `patch`)
}
// 把 root element 赋值给 组件的vnode.el ,为后续调用 $el 的时候获取值
// 是为了将组件和组件内部的根节点联系起来,以便正确地进行组件的初始化和挂载。
initialVNode.el = subTree.el
}
// mounted hook
if (m) { //mount放在post类型cheduler进行异步处理
queuePostRenderEffect(m, parentSuspense)
}
// onVnodeMounted
if (
!isAsyncWrapperVNode &&
(vnodeHook = props && props.onVnodeMounted)
) {
const scopedInitialVNode = initialVNode
queuePostRenderEffect(
() => invokeVNodeHook(vnodeHook!, parent, scopedInitialVNode),
parentSuspense
)
}
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
queuePostRenderEffect(
() => instance.emit('hook:mounted'),
parentSuspense
)
}
// activated hook for keep-alive roots.
// #1742 activated hook must be accessed after first render
// since the hook may be injected by a child keep-alive
if (
initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE ||
(parent &&
isAsyncWrapper(parent.vnode) &&
parent.vnode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE)
) {
instance.a && queuePostRenderEffect(instance.a, parentSuspense)
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
queuePostRenderEffect(
() => instance.emit('hook:activated'),
parentSuspense
)
}
}
instance.isMounted = true
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
devtoolsComponentAdded(instance)
}
// #2458: deference mount-only object parameters to prevent memleaks
initialVNode = container = anchor = null as any
} else {
//更新组件逻辑
// updateComponent
// This is triggered by mutation of component's own state (next: null)
// OR parent calling processComponent (next: VNode)
let { next, bu, u, parent, vnode } = instance
let originNext = next
let vnodeHook: VNodeHook | null | undefined
if (__DEV__) {
pushWarningContext(next || instance.vnode)
}
// Disallow component effect recursion during pre-lifecycle hooks.
toggleRecurse(instance, false)
// 如果有 next 的话, 说明需要更新组件的数据(props,slots 等)
// 先更新组件的数据,然后更新完成后,在继续对比当前组件的子元素
if (next) {
//父组件更新调用processComponet触发子组件更新
//新组件 next 的 el 属性指向老组件 vnode 的 el 属性
// 让新组件直接使用旧组件的 el 对象
//instance.next = n2----updateComponent函数
//说明next只是虚拟节点,next此时并没有el属性
next.el = vnode.el
updateComponentPreRender(instance, next, optimized)
} else {
//组件自身的状态变化,触发组件更新
next = vnode
}
// beforeUpdate hook
if (bu) {
invokeArrayFns(bu)
}
// onVnodeBeforeUpdate
if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
invokeVNodeHook(vnodeHook, parent, next, vnode)
}
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
instance.emit('hook:beforeUpdate')
}
toggleRecurse(instance, true)
// render
if (__DEV__) {
startMeasure(instance, `render`)
}
// 调用render函数--重新进行依赖收集和节点创建并赋值给nextTree
const nextTree = renderComponentRoot(instance)
if (__DEV__) {
endMeasure(instance, `render`)
}
const prevTree = instance.subTree
instance.subTree = nextTree
if (__DEV__) {
startMeasure(instance, `patch`)
}
//patch prevTree和nextTree
patch(
prevTree,
nextTree,
// parent may have changed if it's in a teleport
hostParentNode(prevTree.el!)!,
// anchor may have changed if it's in a fragment
getNextHostNode(prevTree),
instance,
parentSuspense,
isSVG
)
if (__DEV__) {
endMeasure(instance, `patch`)
}
next.el = nextTree.el
if (originNext === null) {
// self-triggered update. In case of HOC, update parent component
// vnode el. HOC is indicated by parent instance's subTree pointing
// to child component's vnode
updateHOCHostEl(instance, nextTree.el)
}
// updated hook
if (u) { //updated在post类型scheduler进行异步处理
queuePostRenderEffect(u, parentSuspense)
}
// onVnodeUpdated
if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
queuePostRenderEffect(
() => invokeVNodeHook(vnodeHook!, parent, next!, vnode),
parentSuspense
)
}
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
queuePostRenderEffect(
() => instance.emit('hook:updated'),
parentSuspense
)
}
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
devtoolsComponentUpdated(instance)
}
if (__DEV__) {
popWarningContext()
}
}
}