Skip to content

feat(runtime-vapor): mounted & unmounted hook #46

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions packages/runtime-vapor/src/apiLifecycle.ts
Original file line number Diff line number Diff line change
@@ -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)
125 changes: 123 additions & 2 deletions packages/runtime-vapor/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from './componentProps'

import type { Data } from '@vue/shared'
import { VaporLifecycleHooks } from './apiLifecycle'

export type Component = FunctionalComponent | ObjectComponent

Expand All @@ -24,6 +25,8 @@ export interface ObjectComponent {
render(ctx: any): Block
}

type LifecycleHook<TFn = Function> = TFn[] | null

export interface ComponentInternalInstance {
uid: number
container: ParentNode
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
}
1 change: 1 addition & 0 deletions packages/runtime-vapor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
27 changes: 17 additions & 10 deletions packages/runtime-vapor/src/render.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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)
}
21 changes: 20 additions & 1 deletion playground/src/App.vue
Original file line number Diff line number Diff line change
@@ -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>
Expand Down