diff --git a/packages/runtime-vapor/src/apiLifecycle.ts b/packages/runtime-vapor/src/apiLifecycle.ts new file mode 100644 index 000000000..eff675728 --- /dev/null +++ b/packages/runtime-vapor/src/apiLifecycle.ts @@ -0,0 +1,48 @@ +import { type ComponentInternalInstance, currentInstance } from './component' + +export enum VaporLifecycleHooks { + 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', +} + +export const injectHook = ( + type: VaporLifecycleHooks, + hook: Function, + target: ComponentInternalInstance | null = currentInstance, + prepend: boolean = false, +) => { + if (target) { + const hooks = target[type] || (target[type] = []) + if (prepend) { + hooks.unshift(hook) + } else { + hooks.push(hook) + } + return hook + } else if (__DEV__) { + // TODO: warn need + } +} +export const createHook = + any>(lifecycle: VaporLifecycleHooks) => + (hook: T, target: ComponentInternalInstance | null = currentInstance) => + injectHook(lifecycle, (...args: unknown[]) => hook(...args), target) + +export const onBeforeMount = createHook(VaporLifecycleHooks.BEFORE_MOUNT) +export const onMounted = createHook(VaporLifecycleHooks.MOUNTED) +export const onBeforeUpdate = createHook(VaporLifecycleHooks.BEFORE_UPDATE) +export const onUpdated = createHook(VaporLifecycleHooks.UPDATED) +export const onBeforeUnmount = createHook(VaporLifecycleHooks.BEFORE_UNMOUNT) +export const onUnmounted = createHook(VaporLifecycleHooks.UNMOUNTED) diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 68e3c6942..23b5f0770 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -10,6 +10,7 @@ import { } from './componentProps' import type { Data } from '@vue/shared' +import { VaporLifecycleHooks } from './apiLifecycle' export type Component = FunctionalComponent | ObjectComponent @@ -24,6 +25,8 @@ export interface ObjectComponent { render(ctx: any): Block } +type LifecycleHook = TFn[] | null + export interface ComponentInternalInstance { uid: number container: ParentNode @@ -44,8 +47,66 @@ export interface ComponentInternalInstance { // lifecycle get isMounted(): boolean + get isUnmounted(): boolean + isUnmountedRef: Ref isMountedRef: Ref // TODO: registory of provides, appContext, lifecycles, ... + /** + * @internal + */ + [VaporLifecycleHooks.BEFORE_CREATE]: LifecycleHook + /** + * @internal + */ + [VaporLifecycleHooks.CREATED]: LifecycleHook + /** + * @internal + */ + [VaporLifecycleHooks.BEFORE_MOUNT]: LifecycleHook + /** + * @internal + */ + [VaporLifecycleHooks.MOUNTED]: LifecycleHook + /** + * @internal + */ + [VaporLifecycleHooks.BEFORE_UPDATE]: LifecycleHook + /** + * @internal + */ + [VaporLifecycleHooks.UPDATED]: LifecycleHook + /** + * @internal + */ + [VaporLifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook + /** + * @internal + */ + [VaporLifecycleHooks.UNMOUNTED]: LifecycleHook + /** + * @internal + */ + [VaporLifecycleHooks.RENDER_TRACKED]: LifecycleHook + /** + * @internal + */ + [VaporLifecycleHooks.RENDER_TRIGGERED]: LifecycleHook + /** + * @internal + */ + [VaporLifecycleHooks.ACTIVATED]: LifecycleHook + /** + * @internal + */ + [VaporLifecycleHooks.DEACTIVATED]: LifecycleHook + /** + * @internal + */ + [VaporLifecycleHooks.ERROR_CAPTURED]: LifecycleHook + /** + * @internal + */ + // [VaporLifecycleHooks.SERVER_PREFETCH]: LifecycleHook<() => Promise> } // TODO @@ -67,17 +128,17 @@ export const createComponentInstance = ( component: ObjectComponent | FunctionalComponent, ): ComponentInternalInstance => { const isMountedRef = ref(false) + const isUnmountedRef = ref(false) const instance: ComponentInternalInstance = { uid: uid++, block: null, - container: null!, // set on mount + container: null!, // set on mountComponent scope: new EffectScope(true /* detached */)!, component, // resolved props and emits options propsOptions: normalizePropsOptions(component), // emitsOptions: normalizeEmitsOptions(type, appContext), // TODO: - proxy: null, // state @@ -90,8 +151,68 @@ export const createComponentInstance = ( get isMounted() { return isMountedRef.value }, + get isUnmounted() { + return isUnmountedRef.value + }, isMountedRef, + isUnmountedRef, // TODO: registory of provides, appContext, lifecycles, ... + /** + * @internal + */ + [VaporLifecycleHooks.BEFORE_CREATE]: null, + /** + * @internal + */ + [VaporLifecycleHooks.CREATED]: null, + /** + * @internal + */ + [VaporLifecycleHooks.BEFORE_MOUNT]: null, + /** + * @internal + */ + [VaporLifecycleHooks.MOUNTED]: null, + /** + * @internal + */ + [VaporLifecycleHooks.BEFORE_UPDATE]: null, + /** + * @internal + */ + [VaporLifecycleHooks.UPDATED]: null, + /** + * @internal + */ + [VaporLifecycleHooks.BEFORE_UNMOUNT]: null, + /** + * @internal + */ + [VaporLifecycleHooks.UNMOUNTED]: null, + /** + * @internal + */ + [VaporLifecycleHooks.RENDER_TRACKED]: null, + /** + * @internal + */ + [VaporLifecycleHooks.RENDER_TRIGGERED]: null, + /** + * @internal + */ + [VaporLifecycleHooks.ACTIVATED]: null, + /** + * @internal + */ + [VaporLifecycleHooks.DEACTIVATED]: null, + /** + * @internal + */ + [VaporLifecycleHooks.ERROR_CAPTURED]: null, + /** + * @internal + */ + // [VaporLifecycleHooks.SERVER_PREFETCH]: null, } return instance } diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index 3a88a738c..7f6165677 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -44,4 +44,5 @@ export * from './scheduler' export * from './directive' export * from './dom' export * from './directives/vShow' +export * from './apiLifecycle' export { getCurrentInstance, type ComponentInternalInstance } from './component' diff --git a/packages/runtime-vapor/src/render.ts b/packages/runtime-vapor/src/render.ts index 123c9f5d8..a2f505ecd 100644 --- a/packages/runtime-vapor/src/render.ts +++ b/packages/runtime-vapor/src/render.ts @@ -1,5 +1,5 @@ import { markRaw, proxyRefs } from '@vue/reactivity' -import { type Data } from '@vue/shared' +import { invokeArrayFns, type Data } from '@vue/shared' import { type Component, type ComponentInternalInstance, @@ -62,30 +62,37 @@ export function mountComponent( } return (instance.block = block) })! + const { bm, m } = instance + + // hook: beforeMount + bm && invokeArrayFns(bm) invokeDirectiveHook(instance, 'beforeMount') + insert(block, instance.container) instance.isMountedRef.value = true + + // hook: mounted invokeDirectiveHook(instance, 'mounted') + m && invokeArrayFns(m) unsetCurrentInstance() - // TODO: lifecycle hooks (mounted, ...) - // const { m } = instance - // m && invoke(m) - return instance } export function unmountComponent(instance: ComponentInternalInstance) { - const { container, block, scope } = instance + const { container, block, scope, um, bum } = instance + // hook: beforeUnmount + bum && invokeArrayFns(bum) invokeDirectiveHook(instance, 'beforeUnmount') + scope.stop() block && remove(block, container) instance.isMountedRef.value = false + instance.isUnmountedRef.value = true + + // hook: unmounted invokeDirectiveHook(instance, 'unmounted') + um && invokeArrayFns(um) unsetCurrentInstance() - - // TODO: lifecycle hooks (unmounted, ...) - // const { um } = instance - // um && invoke(um) } diff --git a/playground/src/App.vue b/playground/src/App.vue index eb83971a3..7e0abdccc 100644 --- a/playground/src/App.vue +++ b/playground/src/App.vue @@ -1,12 +1,31 @@