Vue lifecycle 实现
1. Vue2.x
在看 Vue2.x 源码的时候,总会看到下面类似代码
1 2
| callHook(vm, 'beforeCreate', undefined, false ) callHook(vm, 'created')
|
看一下 callHook 源码
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
| export function callHook( vm: Component, hook: string, args?: any[], setContext = true ) { pushTarget() const prev = currentInstance setContext && setCurrentInstance(vm) const handlers = vm.$options[hook] const info = `${hook} hook` if (handlers) { for (let i = 0, j = handlers.length; i < j; i++) { invokeWithErrorHandling(handlers[i], vm, args || null, vm, info) } } if (vm._hasHookEvent) { vm.$emit('hook:' + hook) } setContext && setCurrentInstance(prev) popTarget() }
|
传入 hook 名称,从当前 vm 获取到 handlers,然后遍历调用 invokeWithErrorHandling, 执行 hook 并且捕获错误
如果 ok,触发 vm.$emit('hook:' + hook),那么剩下的问题就是在哪里解析的?
其实就是在合并 options 的时候 把 vm.constructor.options 合并到 vm.$options 当中,就可以调用了
2. Vue3.x
packages\runtime-core\src\apiLifecycle.ts
1 2 3 4 5 6 7
| export const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT)
export const createHook = <T extends Function = () => any>(lifecycle: LifecycleHooks) => (hook: T, target: ComponentInternalInstance | null = currentInstance) => (!isInSSRComponentSetup || lifecycle === LifecycleHooks.SERVER_PREFETCH) && injectHook(lifecycle, (...args: unknown[]) => hook(...args), target)
|
分一下断就是
1 2 3 4 5 6 7
| (lifecycle: LifecycleHooks) => { return (hook: T, target: ComponentInternalInstance | null = currentInstance) => { if(!isInSSRComponentSetup || lifecycle === LifecycleHooks.SERVER_PREFETCH) { return injectHook(lifecycle, (...args: unknown[]) => hook(...args), target) } } }
|
就是闭包,或者说是工厂函数,用来调用 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 57 58
| export function injectHook( type: LifecycleHooks, hook: Function & { __weh?: Function }, target: ComponentInternalInstance | null = currentInstance, prepend: boolean = false ): Function | undefined { if (target) { const hooks = target[type] || (target[type] = []) const wrappedHook = hook.__weh || (hook.__weh = (...args: unknown[]) => { if (target.isUnmounted) { return }
pauseTracking() setCurrentInstance(target) const res = callWithAsyncErrorHandling(hook, target, type, args) unsetCurrentInstance() resetTracking() return res })
if (prepend) { hooks.unshift(wrappedHook) } else { hooks.push(wrappedHook) }
return wrappedHook } else if (__DEV__) { const apiName = toHandlerKey(ErrorTypeStrings[type].replace(/ hook$/, '')) warn( ) } }
|
也就是说, inject 会给 target 注入一个函数,函数可以直接执行 hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { onBeforeMount, ref } from 'vue';
export default { setup() { const count = ref(0);
onBeforeMount(() => { console.log('组件挂载之前'); });
return { count }; } };
|
ok, 到这里明白了生命周期是如何注入的,那么 Vue3 生命周期如何调用呢?,以 beforeMount 为例子
这个时候找到了 setupRenderEffect 也就是渲染 setup 的函数
1 2 3 4 5 6 7 8 9
| const effect = (instance.effect = new ReactiveEffect( componentUpdateFn, () => queueJob(update), instance.scope ))
const update: SchedulerJob = (instance.update = () => effect.run()) update.id = instance.uid update()
|
另外除了这样注册之外,还可以直接这样调用
1
| invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount')
|
会直接触发,而不需要等待事件。
3. 总结
Vue2.x Vue3.x 生命周期的注入和调用,本质上没什么区别。
都是挂在在当前上下文,区别只是通知方式,一个直接调用,一个通过 emit 来触发