]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(runtime-vapor): mounted & unmounted hook (#46)
authorGaoNeng <31283122+GaoNeng-wWw@users.noreply.github.com>
Thu, 14 Dec 2023 17:47:56 +0000 (01:47 +0800)
committerGitHub <noreply@github.com>
Thu, 14 Dec 2023 17:47:56 +0000 (01:47 +0800)
Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
packages/runtime-vapor/src/apiLifecycle.ts [new file with mode: 0644]
packages/runtime-vapor/src/component.ts
packages/runtime-vapor/src/index.ts
packages/runtime-vapor/src/render.ts
playground/src/App.vue

diff --git a/packages/runtime-vapor/src/apiLifecycle.ts b/packages/runtime-vapor/src/apiLifecycle.ts
new file mode 100644 (file)
index 0000000..eff6757
--- /dev/null
@@ -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 =
+  <T extends Function = () => 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)
index 68e3c6942fcf2a859f77fcdbfc7adbff7f3ca34a..23b5f0770e38ee3191447cbe1f580b9570c0fe2c 100644 (file)
@@ -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 = Function> = 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<boolean>
   isMountedRef: Ref<boolean>
   // 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<unknown>>
 }
 
 // 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
 }
index 3a88a738c914e772235d1942b3167aee8ed0a8b5..7f61656775c2a669dc17ded7788e784fc00bd6a3 100644 (file)
@@ -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'
index 123c9f5d8fff657170196636bca8767da8a5e6df..a2f505ecdeb3c1a9192ea85d7a9b588992736a4a 100644 (file)
@@ -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)
 }
index eb83971a3e0ef6f13c9f19892e1a8a47600949ec..7e0abdccc70be6ddcaf3df0f0d79e5470b803ccf 100644 (file)
@@ -1,12 +1,31 @@
 <script setup lang="ts">
-import { ref, computed } from 'vue/vapor'
+import {
+  ref,
+  computed,
+  onMounted,
+  onBeforeMount,
+  getCurrentInstance
+} from 'vue/vapor'
 
+const instance = getCurrentInstance()!
 const count = ref(1)
 const double = computed(() => count.value * 2)
 const html = computed(() => `<button>HTML! ${count.value}</button>`)
 
 const inc = () => count.value++
 const dec = () => count.value--
+
+onBeforeMount(() => {
+  console.log('onBeforeMount', instance.isMounted)
+})
+onMounted(() => {
+  console.log('onMounted', instance.isMounted)
+})
+onMounted(() => {
+  setTimeout(() => {
+    count.value++
+  }, 1000)
+})
 </script>
 
 <template>