From e23a6a87460a2d1e3e96832fc93e9b92ce093cf6 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 5 Dec 2024 23:13:24 +0800 Subject: [PATCH] wip: unify currentInstance between vdom and vapor + provide/inject --- packages/runtime-core/src/apiInject.ts | 10 +- packages/runtime-core/src/apiLifecycle.ts | 10 +- packages/runtime-core/src/component.ts | 116 +++++++++--------- .../src/componentCurrentInstance.ts | 27 +++- packages/runtime-core/src/componentOptions.ts | 8 +- .../runtime-core/src/components/KeepAlive.ts | 3 +- packages/runtime-core/src/index.ts | 1 + packages/runtime-vapor/src/component.ts | 51 +++++--- packages/runtime-vapor/src/renderEffect.ts | 3 +- 9 files changed, 131 insertions(+), 98 deletions(-) diff --git a/packages/runtime-core/src/apiInject.ts b/packages/runtime-core/src/apiInject.ts index f05d7333d..d12fef248 100644 --- a/packages/runtime-core/src/apiInject.ts +++ b/packages/runtime-core/src/apiInject.ts @@ -1,6 +1,5 @@ import { isFunction } from '@vue/shared' -import { currentInstance } from './component' -import { currentRenderingInstance } from './componentRenderContext' +import { getCurrentGenericInstance } from './component' import { currentApp } from './apiCreateApp' import { warn } from './warning' @@ -12,6 +11,7 @@ export function provide | string | number>( key: K, value: K extends InjectionKey ? V : T, ): void { + const currentInstance = getCurrentGenericInstance() if (!currentInstance) { if (__DEV__) { warn(`provide() can only be used inside setup().`) @@ -51,7 +51,7 @@ export function inject( ) { // fallback to `currentRenderingInstance` so that this can be called in // a functional component - const instance = currentInstance || currentRenderingInstance + const instance = getCurrentGenericInstance() // also support looking up from app-level provides w/ `app.runWithContext()` if (instance || currentApp) { @@ -63,7 +63,7 @@ export function inject( ? currentApp._context.provides : instance ? instance.parent == null - ? instance.vnode.appContext && instance.vnode.appContext.provides + ? instance.appContext && instance.appContext.provides : instance.parent.provides : undefined @@ -88,5 +88,5 @@ export function inject( * user. One example is `useRoute()` in `vue-router`. */ export function hasInjectionContext(): boolean { - return !!(currentInstance || currentRenderingInstance || currentApp) + return !!(getCurrentGenericInstance() || currentApp) } diff --git a/packages/runtime-core/src/apiLifecycle.ts b/packages/runtime-core/src/apiLifecycle.ts index b79a6d38a..550ae914a 100644 --- a/packages/runtime-core/src/apiLifecycle.ts +++ b/packages/runtime-core/src/apiLifecycle.ts @@ -1,5 +1,5 @@ import { - type ComponentInternalInstance, + type GenericComponentInstance, currentInstance, isInSSRComponentSetup, setCurrentInstance, @@ -20,7 +20,7 @@ export { onActivated, onDeactivated } from './components/KeepAlive' export function injectHook( type: LifecycleHooks, hook: Function & { __weh?: Function }, - target: ComponentInternalInstance | null = currentInstance, + target: GenericComponentInstance | null = currentInstance, prepend: boolean = false, ): Function | undefined { if (target) { @@ -67,7 +67,7 @@ const createHook = any>(lifecycle: LifecycleHooks) => ( hook: T, - target: ComponentInternalInstance | null = currentInstance, + target: GenericComponentInstance | null = currentInstance, ): void => { // post-create lifecycle registrations are noops during SSR (except for serverPrefetch) if ( @@ -79,7 +79,7 @@ const createHook = } type CreateHook = ( hook: T, - target?: ComponentInternalInstance | null, + target?: GenericComponentInstance | null, ) => void export const onBeforeMount: CreateHook = createHook(LifecycleHooks.BEFORE_MOUNT) @@ -110,7 +110,7 @@ export type ErrorCapturedHook = ( export function onErrorCaptured( hook: ErrorCapturedHook, - target: ComponentInternalInstance | null = currentInstance, + target: GenericComponentInstance | null = currentInstance, ): void { injectHook(LifecycleHooks.ERROR_CAPTURED, hook, target) } diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index c07d468f6..f963ad81f 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -332,6 +332,7 @@ export type InternalRenderFunction = { * operate on both. */ export interface GenericComponentInstance { + vapor?: boolean uid: number type: GenericComponent parent: GenericComponentInstance | null @@ -395,10 +396,64 @@ export interface GenericComponentInstance { // the following are for error handling logic only proxy?: any + + // lifecycle /** * @internal */ - [LifecycleHooks.ERROR_CAPTURED]: LifecycleHook + [LifecycleHooks.BEFORE_CREATE]?: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.CREATED]?: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.BEFORE_MOUNT]?: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.MOUNTED]?: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.BEFORE_UPDATE]?: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.UPDATED]?: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.BEFORE_UNMOUNT]?: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.UNMOUNTED]?: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.RENDER_TRACKED]?: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.RENDER_TRIGGERED]?: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.ACTIVATED]?: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.DEACTIVATED]?: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.ERROR_CAPTURED]?: LifecycleHook + /** + * @internal + */ + [LifecycleHooks.SERVER_PREFETCH]?: LifecycleHook<() => Promise> } /** @@ -406,6 +461,7 @@ export interface GenericComponentInstance { * useful for advanced external libraries and tools. */ export interface ComponentInternalInstance extends GenericComponentInstance { + vapor?: never uid: number type: ConcreteComponent parent: ComponentInternalInstance | null @@ -563,64 +619,6 @@ export interface ComponentInternalInstance extends GenericComponentInstance { */ asyncResolved: boolean - // lifecycle - /** - * @internal - */ - [LifecycleHooks.BEFORE_CREATE]: LifecycleHook - /** - * @internal - */ - [LifecycleHooks.CREATED]: LifecycleHook - /** - * @internal - */ - [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook - /** - * @internal - */ - [LifecycleHooks.MOUNTED]: LifecycleHook - /** - * @internal - */ - [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook - /** - * @internal - */ - [LifecycleHooks.UPDATED]: LifecycleHook - /** - * @internal - */ - [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook - /** - * @internal - */ - [LifecycleHooks.UNMOUNTED]: LifecycleHook - /** - * @internal - */ - [LifecycleHooks.RENDER_TRACKED]: LifecycleHook - /** - * @internal - */ - [LifecycleHooks.RENDER_TRIGGERED]: LifecycleHook - /** - * @internal - */ - [LifecycleHooks.ACTIVATED]: LifecycleHook - /** - * @internal - */ - [LifecycleHooks.DEACTIVATED]: LifecycleHook - /** - * @internal - */ - [LifecycleHooks.ERROR_CAPTURED]: LifecycleHook - /** - * @internal - */ - [LifecycleHooks.SERVER_PREFETCH]: LifecycleHook<() => Promise> - /** * For caching bound $forceUpdate on public proxy access * @internal diff --git a/packages/runtime-core/src/componentCurrentInstance.ts b/packages/runtime-core/src/componentCurrentInstance.ts index 268a40081..b2170a164 100644 --- a/packages/runtime-core/src/componentCurrentInstance.ts +++ b/packages/runtime-core/src/componentCurrentInstance.ts @@ -1,18 +1,32 @@ import { getGlobalThis } from '@vue/shared' -import type { ComponentInternalInstance } from './component' +import type { + ComponentInternalInstance, + GenericComponentInstance, +} from './component' import { currentRenderingInstance } from './componentRenderContext' -export let currentInstance: ComponentInternalInstance | null = null +/** + * @internal + */ +export let currentInstance: GenericComponentInstance | null = null + +/** + * @internal + */ +export const getCurrentGenericInstance: () => GenericComponentInstance | null = + () => currentInstance || currentRenderingInstance export const getCurrentInstance: () => ComponentInternalInstance | null = () => - currentInstance || currentRenderingInstance + currentInstance && !currentInstance.vapor + ? (currentInstance as ComponentInternalInstance) + : currentRenderingInstance export let isInSSRComponentSetup = false export let setInSSRSetupState: (state: boolean) => void let internalSetCurrentInstance: ( - instance: ComponentInternalInstance | null, + instance: GenericComponentInstance | null, ) => void /** @@ -60,7 +74,10 @@ if (__SSR__) { } } -export const setCurrentInstance = (instance: ComponentInternalInstance) => { +/** + * @internal + */ +export const setCurrentInstance = (instance: GenericComponentInstance) => { const prev = currentInstance internalSetCurrentInstance(instance) instance.scope.on() diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts index f864f39e4..75fbbd77a 100644 --- a/packages/runtime-core/src/componentOptions.ts +++ b/packages/runtime-core/src/componentOptions.ts @@ -6,7 +6,7 @@ import { type Data, type InternalRenderFunction, type SetupContext, - currentInstance, + getCurrentInstance, } from './component' import { type LooseRequired, @@ -855,10 +855,8 @@ export function createWatcher( const options: WatchOptions = {} if (__COMPAT__) { - const instance = - currentInstance && getCurrentScope() === currentInstance.scope - ? currentInstance - : null + const cur = getCurrentInstance() + const instance = cur && getCurrentScope() === cur.scope ? cur : null const newValue = getter() if ( diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index 5976f3a4b..b0fb27207 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -3,7 +3,6 @@ import { type ComponentOptions, type ConcreteComponent, type SetupContext, - currentInstance, getComponentName, getCurrentInstance, } from '../component' @@ -411,7 +410,7 @@ export function onDeactivated( function registerKeepAliveHook( hook: Function & { __wdc?: Function }, type: LifecycleHooks, - target: ComponentInternalInstance | null = currentInstance, + target: ComponentInternalInstance | null = getCurrentInstance(), ) { // cache the deactivate branch check wrapper for injected hooks so the same // hook can be properly deduped by the scheduler. "__wdc" stands for "with diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 589cdaac2..eb5f44896 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -507,3 +507,4 @@ export { type AppMountFn, type AppUnmountFn, } from './apiCreateApp' +export { currentInstance, setCurrentInstance } from './componentCurrentInstance' diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 614054a3b..96f5552f9 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -9,9 +9,11 @@ import { type LifecycleHook, type NormalizedPropsOptions, type ObjectEmitsOptions, + currentInstance, nextUid, popWarningContext, pushWarningContext, + setCurrentInstance, warn, } from '@vue/runtime-dom' import { type Block, isBlock } from './block' @@ -28,6 +30,8 @@ import { setDynamicProp } from './dom/prop' import { renderEffect } from './renderEffect' import { emit, normalizeEmitsOptions } from './componentEmits' +export { currentInstance } from '@vue/runtime-dom' + export type VaporComponent = FunctionalVaporComponent | ObjectVaporComponent export type VaporSetupFn = ( @@ -77,7 +81,11 @@ export function createComponent( ): VaporComponentInstance { // check if we are the single root of the parent // if yes, inject parent attrs as dynamic props source - if (isSingleRoot && currentInstance && currentInstance.hasFallthrough) { + if ( + isSingleRoot && + isVaporComponent(currentInstance) && + currentInstance.hasFallthrough + ) { const attrs = currentInstance.attrs if (rawProps) { ;(rawProps.$ || (rawProps.$ = [])).push(() => attrs) @@ -87,12 +95,9 @@ export function createComponent( } const instance = new VaporComponentInstance(component, rawProps) + const resetCurrentInstance = setCurrentInstance(instance) pauseTracking() - let prevInstance = currentInstance - currentInstance = instance - instance.scope.on() - if (__DEV__) { pushWarningContext(instance) } @@ -140,15 +145,12 @@ export function createComponent( if (__DEV__) { popWarningContext() } - - instance.scope.off() - currentInstance = prevInstance resetTracking() + resetCurrentInstance() + return instance } -export let currentInstance: VaporComponentInstance | null = null - const emptyContext: GenericAppContext = { app: null as any, config: {}, @@ -156,6 +158,7 @@ const emptyContext: GenericAppContext = { } export class VaporComponentInstance implements GenericComponentInstance { + vapor: true uid: number type: VaporComponent parent: GenericComponentInstance | null @@ -178,11 +181,25 @@ export class VaporComponentInstance implements GenericComponentInstance { hasFallthrough: boolean + // lifecycle hooks isMounted: boolean isUnmounted: boolean isDeactivated: boolean - // LifecycleHooks.ERROR_CAPTURED - ec: LifecycleHook + + bc?: LifecycleHook // LifecycleHooks.BEFORE_CREATE + c?: LifecycleHook // LifecycleHooks.CREATED + bm?: LifecycleHook // LifecycleHooks.BEFORE_MOUNT + m?: LifecycleHook // LifecycleHooks.MOUNTED + bu?: LifecycleHook // LifecycleHooks.BEFORE_UPDATE + u?: LifecycleHook // LifecycleHooks.UPDATED + um?: LifecycleHook // LifecycleHooks.BEFORE_UNMOUNT + bum?: LifecycleHook // LifecycleHooks.UNMOUNTED + da?: LifecycleHook // LifecycleHooks.DEACTIVATED + a?: LifecycleHook // LifecycleHooks.ACTIVATED + rtg?: LifecycleHook // LifecycleHooks.RENDER_TRACKED + rtc?: LifecycleHook // LifecycleHooks.RENDER_TRIGGERED + ec?: LifecycleHook // LifecycleHooks.ERROR_CAPTURED + sp?: LifecycleHook<() => Promise> // LifecycleHooks.SERVER_PREFETCH // dev only setupState?: Record @@ -190,9 +207,10 @@ export class VaporComponentInstance implements GenericComponentInstance { emitsOptions?: ObjectEmitsOptions | null constructor(comp: VaporComponent, rawProps?: RawProps) { + this.vapor = true this.uid = nextUid() this.type = comp - this.parent = currentInstance + this.parent = currentInstance // TODO when inside this.appContext = currentInstance ? currentInstance.appContext : emptyContext @@ -200,14 +218,17 @@ export class VaporComponentInstance implements GenericComponentInstance { this.block = null! // to be set this.scope = new EffectScope(true) - this.rawProps = rawProps - this.provides = this.refs = EMPTY_OBJ + this.provides = currentInstance + ? currentInstance.provides + : Object.create(this.appContext.provides) + this.refs = EMPTY_OBJ this.emitted = this.ec = this.exposed = this.propsDefaults = null this.isMounted = this.isUnmounted = this.isDeactivated = false // init props const target = rawProps || EMPTY_OBJ const handlers = getPropsProxyHandlers(comp, this) + this.rawProps = rawProps this.props = comp.props ? new Proxy(target, handlers[0]!) : {} this.attrs = new Proxy(target, handlers[1]) this.hasFallthrough = hasFallthroughAttrs(comp, rawProps) diff --git a/packages/runtime-vapor/src/renderEffect.ts b/packages/runtime-vapor/src/renderEffect.ts index 170f29bb4..6721661a0 100644 --- a/packages/runtime-vapor/src/renderEffect.ts +++ b/packages/runtime-vapor/src/renderEffect.ts @@ -1,6 +1,5 @@ import { ReactiveEffect } from '@vue/reactivity' -import { type SchedulerJob, queueJob } from '@vue/runtime-dom' -import { currentInstance } from './component' +import { type SchedulerJob, currentInstance, queueJob } from '@vue/runtime-dom' export function renderEffect(fn: () => void): void { const updateFn = () => {