diff --git a/packages/runtime-vapor/src/apiCreateComponent.ts b/packages/runtime-vapor/src/apiCreateComponent.ts index 22acecb07..622024f85 100644 --- a/packages/runtime-vapor/src/apiCreateComponent.ts +++ b/packages/runtime-vapor/src/apiCreateComponent.ts @@ -1,21 +1,13 @@ -import { - type Component, - createComponentInstance, - setCurrentInstance, -} from './component' +import { type Component, createComponentInstance } from './component' import { setupComponent } from './apiRender' import type { RawProps } from './componentProps' import type { RawSlots } from './componentSlots' import { withAttrs } from './componentAttrs' import { getCurrentScope } from '@vue/reactivity' import type { BlockEffectScope } from './blockEffectScope' -import { - type Directive, - type DirectiveHookName, - invokeDirectiveHook, -} from './directives' +import { setDirectiveBinding } from './directives' import { VaporLifecycleHooks } from './enums' -import { NOOP, invokeArrayFns } from '@vue/shared' +import { scheduleLifecycleHooks } from './componentLifecycle' export function createComponent( comp: Component, @@ -33,58 +25,39 @@ export function createComponent( ) setupComponent(instance, singleRoot) - const directiveBindingsMap = (parentScope.dirs ||= new Map()) - const dir: Directive = { - beforeMount: passDirectives( - VaporLifecycleHooks.BEFORE_MOUNT, - 'beforeMount', - ), - mounted: passDirectives( - VaporLifecycleHooks.MOUNTED, - 'mounted', - () => (instance.isMounted = true), - true, - ), - beforeUnmount: passDirectives( - VaporLifecycleHooks.BEFORE_UNMOUNT, - 'beforeUnmount', - ), - unmounted: passDirectives( - VaporLifecycleHooks.UNMOUNTED, - 'unmounted', - () => (instance.isUnmounted = true), - true, - ), - } - directiveBindingsMap.set(instance, [ - { dir, instance, value: null, oldValue: undefined }, - ]) + setDirectiveBinding( + instance, + instance, + { + beforeMount: scheduleLifecycleHooks( + instance, + VaporLifecycleHooks.BEFORE_MOUNT, + 'beforeMount', + ), + mounted: scheduleLifecycleHooks( + instance, + VaporLifecycleHooks.MOUNTED, + 'mounted', + () => (instance.isMounted = true), + true, + ), + beforeUnmount: scheduleLifecycleHooks( + instance, + VaporLifecycleHooks.BEFORE_UNMOUNT, + 'beforeUnmount', + ), + unmounted: scheduleLifecycleHooks( + instance, + VaporLifecycleHooks.UNMOUNTED, + 'unmounted', + () => (instance.isUnmounted = true), + true, + ), + }, + null, + undefined, + parentScope, + ) return instance - - function passDirectives( - lifecycle: VaporLifecycleHooks, - directive: DirectiveHookName, - cb = NOOP, - reverse?: boolean, - ) { - const hooks = reverse - ? [cb, callDirHooks, callLifecycleHooks] - : [callLifecycleHooks, callDirHooks, cb] - - return () => invokeArrayFns(hooks) - - function callDirHooks() { - invokeDirectiveHook(instance, directive, instance.scope) - } - function callLifecycleHooks() { - // lifecycle hooks may be mounted halfway. - const lifecycleHooks = instance[lifecycle] - if (lifecycleHooks && lifecycleHooks.length) { - const reset = setCurrentInstance(instance) - instance.scope.run(() => invokeArrayFns(lifecycleHooks)) - reset() - } - } - } } diff --git a/packages/runtime-vapor/src/apiCreateFor.ts b/packages/runtime-vapor/src/apiCreateFor.ts index 07bbd151f..f405c7593 100644 --- a/packages/runtime-vapor/src/apiCreateFor.ts +++ b/packages/runtime-vapor/src/apiCreateFor.ts @@ -18,7 +18,7 @@ import { type Block, type Fragment, fragmentKey } from './apiRender' import { warn } from './warning' import { currentInstance } from './component' import { componentKey } from './component' -import { BlockEffectScope, isRenderEffectScope } from './blockEffectScope' +import { BlockEffectScope, isBlockEffectScope } from './blockEffectScope' import { createChildFragmentDirectives, invokeWithMount, @@ -62,7 +62,7 @@ export const createFor = ( } const instance = currentInstance! - if (__DEV__ && (!instance || !isRenderEffectScope(parentScope))) { + if (__DEV__ && (!instance || !isBlockEffectScope(parentScope))) { warn('createFor() can only be used inside setup()') } diff --git a/packages/runtime-vapor/src/apiCreateIf.ts b/packages/runtime-vapor/src/apiCreateIf.ts index dce38bae9..1034f46a1 100644 --- a/packages/runtime-vapor/src/apiCreateIf.ts +++ b/packages/runtime-vapor/src/apiCreateIf.ts @@ -3,7 +3,7 @@ import { getCurrentScope } from '@vue/reactivity' import { createComment, createTextNode, insert, remove } from './dom/element' import { currentInstance } from './component' import { warn } from './warning' -import { BlockEffectScope, isRenderEffectScope } from './blockEffectScope' +import { BlockEffectScope, isBlockEffectScope } from './blockEffectScope' import { createChildFragmentDirectives, invokeWithMount, @@ -36,7 +36,7 @@ export const createIf = ( } const instance = currentInstance! - if (__DEV__ && (!instance || !isRenderEffectScope(parentScope))) { + if (__DEV__ && (!instance || !isBlockEffectScope(parentScope))) { warn('createIf() can only be used inside setup()') } diff --git a/packages/runtime-vapor/src/apiRender.ts b/packages/runtime-vapor/src/apiRender.ts index 086f18d63..d070739b3 100644 --- a/packages/runtime-vapor/src/apiRender.ts +++ b/packages/runtime-vapor/src/apiRender.ts @@ -137,7 +137,7 @@ function mountComponent( instance, VaporLifecycleHooks.MOUNTED, 'mounted', - instance => (instance.isMounted = true), + () => (instance.isMounted = true), true, ) @@ -166,7 +166,7 @@ export function unmountComponent(instance: ComponentInternalInstance) { instance, VaporLifecycleHooks.UNMOUNTED, 'unmounted', - instance => queuePostFlushCb(() => (instance.isUnmounted = true)), + () => (instance.isUnmounted = true), true, ) flushPostFlushCbs() diff --git a/packages/runtime-vapor/src/blockEffectScope.ts b/packages/runtime-vapor/src/blockEffectScope.ts index 92fc01983..c7a8961d5 100644 --- a/packages/runtime-vapor/src/blockEffectScope.ts +++ b/packages/runtime-vapor/src/blockEffectScope.ts @@ -29,7 +29,7 @@ export class BlockEffectScope extends EffectScope { } } -export function isRenderEffectScope( +export function isBlockEffectScope( scope: EffectScope | undefined, ): scope is BlockEffectScope { return scope instanceof BlockEffectScope diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 4c4b02a52..256c98eae 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -162,7 +162,6 @@ export interface ComponentInternalInstance { provides: Data scope: BlockEffectScope - comps: Set rawProps: NormalizedRawProps propsOptions: NormalizedPropsOptions @@ -286,7 +285,6 @@ export function createComponentInstance( scope: null!, provides: parent ? parent.provides : Object.create(_appContext.provides), type: component, - comps: new Set(), // resolved props and emits options rawProps: null!, // set later diff --git a/packages/runtime-vapor/src/componentLifecycle.ts b/packages/runtime-vapor/src/componentLifecycle.ts index 2bff5a0f6..715a4a997 100644 --- a/packages/runtime-vapor/src/componentLifecycle.ts +++ b/packages/runtime-vapor/src/componentLifecycle.ts @@ -1,36 +1,43 @@ -import { invokeArrayFns } from '@vue/shared' +import { NOOP, invokeArrayFns } from '@vue/shared' import type { VaporLifecycleHooks } from './enums' import { type ComponentInternalInstance, setCurrentInstance } from './component' -import { queuePostFlushCb } from './scheduler' import { type DirectiveHookName, invokeDirectiveHook } from './directives' +import { queuePostFlushCb } from './scheduler' export function invokeLifecycle( instance: ComponentInternalInstance, lifecycle: VaporLifecycleHooks, directive: DirectiveHookName, - cb?: (instance: ComponentInternalInstance) => void, + cb?: () => void, post?: boolean, ) { - invokeArrayFns(post ? [invokeSub, invokeCurrent] : [invokeCurrent, invokeSub]) + const fn = scheduleLifecycleHooks(instance, lifecycle, directive, cb, post) + return post ? queuePostFlushCb(fn) : fn() +} - function invokeCurrent() { - cb && cb(instance) - const hooks = instance[lifecycle] - if (hooks) { - const fn = () => { - const reset = setCurrentInstance(instance) - instance.scope.run(() => invokeArrayFns(hooks)) - reset() - } - post ? queuePostFlushCb(fn) : fn() - } +export function scheduleLifecycleHooks( + instance: ComponentInternalInstance, + lifecycle: VaporLifecycleHooks, + directive: DirectiveHookName, + cb = NOOP, + reverse?: boolean, +) { + const hooks = reverse + ? [cb, callDirHooks, callLifecycleHooks] + : [callLifecycleHooks, callDirHooks, cb] + + return () => invokeArrayFns(hooks) + function callDirHooks() { invokeDirectiveHook(instance, directive, instance.scope) } - - function invokeSub() { - instance.comps.forEach(comp => - invokeLifecycle(comp, lifecycle, directive, cb, post), - ) + function callLifecycleHooks() { + // lifecycle hooks may be mounted halfway. + const lifecycleHooks = instance[lifecycle] + if (lifecycleHooks && lifecycleHooks.length) { + const reset = setCurrentInstance(instance) + instance.scope.run(() => invokeArrayFns(lifecycleHooks)) + reset() + } } } diff --git a/packages/runtime-vapor/src/directives.ts b/packages/runtime-vapor/src/directives.ts index 30ea1012f..ff6fa27ef 100644 --- a/packages/runtime-vapor/src/directives.ts +++ b/packages/runtime-vapor/src/directives.ts @@ -21,7 +21,7 @@ import { } from './errorHandling' import { queueJob, queuePostFlushCb } from './scheduler' import { warn } from './warning' -import { type BlockEffectScope, isRenderEffectScope } from './blockEffectScope' +import { type BlockEffectScope, isBlockEffectScope } from './blockEffectScope' import { normalizeBlock } from './dom/element' export type DirectiveModifiers = Record @@ -112,8 +112,8 @@ export function withDirectives( const instance = currentInstance! const parentScope = getCurrentScope() as BlockEffectScope - if (__DEV__ && !isRenderEffectScope(parentScope)) { - warn(`Directives should be used inside of RenderEffectScope.`) + if (__DEV__ && !isBlockEffectScope(parentScope)) { + warn(`Directives should be used inside of BlockEffectScope.`) } const directivesMap = (parentScope.dirs ||= new Map()) @@ -264,3 +264,20 @@ export function createRenderingUpdateTrigger( } } } + +/** For internal use, set directive binding */ +export function setDirectiveBinding( + instance: ComponentInternalInstance | null, + anchor: Node | ComponentInternalInstance, + dir: Directive, + value: any = null, + oldValue: any = undefined, + parentScope = getCurrentScope() as BlockEffectScope, +) { + if (__DEV__ && !isBlockEffectScope(parentScope)) { + warn('directive binding can only be added to BlockEffectScope') + } + + const directiveBindingsMap = (parentScope.dirs ||= new Map()) + directiveBindingsMap.set(anchor, [{ dir, instance, value, oldValue }]) +} diff --git a/packages/runtime-vapor/src/directivesChildFragment.ts b/packages/runtime-vapor/src/directivesChildFragment.ts index ff2f6e72f..75a29b807 100644 --- a/packages/runtime-vapor/src/directivesChildFragment.ts +++ b/packages/runtime-vapor/src/directivesChildFragment.ts @@ -1,12 +1,12 @@ -import { ReactiveEffect, getCurrentScope } from '@vue/reactivity' +import { ReactiveEffect } from '@vue/reactivity' import { - type Directive, type DirectiveHookName, createRenderingUpdateTrigger, invokeDirectiveHook, + setDirectiveBinding, } from './directives' import { warn } from './warning' -import { type BlockEffectScope, isRenderEffectScope } from './blockEffectScope' +import { type BlockEffectScope, isBlockEffectScope } from './blockEffectScope' import { currentInstance } from './component' import { VaporErrorCodes, callWithErrorHandling } from './errorHandling' import { queueJob, queuePostFlushCb } from './scheduler' @@ -25,14 +25,8 @@ export function createChildFragmentDirectives( ) { let isTriggered = false const instance = currentInstance! - const parentScope = getCurrentScope() as BlockEffectScope - if (__DEV__) { - if (!isRenderEffectScope(parentScope)) { - warn('child directives can only be added to a render effect scope') - } - if (!instance) { - warn('child directives can only be added in a component') - } + if (__DEV__ && !instance) { + warn('child directives can only be added in a component') } const callSourceWithErrorHandling = () => @@ -43,22 +37,13 @@ export function createChildFragmentDirectives( return } - const directiveBindingsMap = (parentScope.dirs ||= new Map()) - const dir: Directive = { + setDirectiveBinding(instance, anchor, { beforeUpdate: onDirectiveBeforeUpdate, beforeMount: () => invokeChildrenDirectives('beforeMount'), mounted: () => invokeChildrenDirectives('mounted'), beforeUnmount: () => invokeChildrenDirectives('beforeUnmount'), unmounted: () => invokeChildrenDirectives('unmounted'), - } - directiveBindingsMap.set(anchor, [ - { - dir, - instance, - value: null, - oldValue: undefined, - }, - ]) + }) const effect = new ReactiveEffect(callSourceWithErrorHandling) const triggerRenderingUpdate = createRenderingUpdateTrigger(instance, effect) @@ -93,7 +78,7 @@ export function createChildFragmentDirectives( } export function invokeWithMount(scope: BlockEffectScope, handler?: () => any) { - if (isRenderEffectScope(scope.parent) && !scope.parent.im) { + if (isBlockEffectScope(scope.parent) && !scope.parent.im) { return handler && handler() } return invokeWithDirsHooks(scope, 'mount', handler)