From: daiwei Date: Thu, 4 Dec 2025 04:03:09 +0000 (+0800) Subject: refactor(runtime-vapor): use dedicated currentSlotOwner instead of changing currentIn... X-Git-Tag: v3.6.0-alpha.6~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7e0d8130e6c9f77da878ff7ff1f34b9e668ff700;p=thirdparty%2Fvuejs%2Fcore.git refactor(runtime-vapor): use dedicated currentSlotOwner instead of changing currentInstance in withVaporCtx --- diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 9371c1a1ab..c1098e3879 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -77,10 +77,10 @@ import { type RawSlots, type StaticSlots, type VaporSlot, + currentSlotOwner, dynamicSlotsProxyHandlers, - getParentInstance, getSlot, - setCurrentSlotConsumer, + setCurrentSlotOwner, } from './componentSlots' import { hmrReload, hmrRerender } from './hmr' import { @@ -202,24 +202,22 @@ export function createComponent( resetInsertionState() } - const parentInstance = getParentInstance() - let prevSuspense: SuspenseBoundary | null = null - if (__FEATURE_SUSPENSE__ && parentInstance && parentInstance.suspense) { - prevSuspense = setParentSuspense(parentInstance.suspense) + if (__FEATURE_SUSPENSE__ && currentInstance && currentInstance.suspense) { + prevSuspense = setParentSuspense(currentInstance.suspense) } if ( (isSingleRoot || // transition has attrs fallthrough - (parentInstance && isVaporTransition(parentInstance!.type))) && + (currentInstance && isVaporTransition(currentInstance!.type))) && component.inheritAttrs !== false && - isVaporComponent(parentInstance) && - parentInstance.hasFallthrough + isVaporComponent(currentInstance) && + currentInstance.hasFallthrough ) { // check if we are the single root of the parent // if yes, inject parent attrs as dynamic props source - const attrs = parentInstance.attrs + const attrs = currentInstance.attrs if (rawProps && rawProps !== EMPTY_OBJ) { ;((rawProps as RawProps).$ || ((rawProps as RawProps).$ = [])).push( () => attrs, @@ -230,8 +228,12 @@ export function createComponent( } // keep-alive - if (parentInstance && parentInstance.vapor && isKeepAlive(parentInstance)) { - const cached = (parentInstance as KeepAliveInstance).getCachedComponent( + if ( + currentInstance && + currentInstance.vapor && + isKeepAlive(currentInstance) + ) { + const cached = (currentInstance as KeepAliveInstance).getCachedComponent( component, ) // @ts-expect-error @@ -240,14 +242,12 @@ export function createComponent( // vdom interop enabled and component is not an explicit vapor component if (appContext.vapor && !component.__vapor) { - const prevSlotConsumer = setCurrentSlotConsumer(null) const frag = appContext.vapor.vdomMount( component as any, - parentInstance as any, + currentInstance as any, rawProps, rawSlots, ) - setCurrentSlotConsumer(prevSlotConsumer) if (!isHydrating) { if (_insertionParent) insert(frag, _insertionParent, _insertionAnchor) } else { @@ -280,11 +280,10 @@ export function createComponent( rawSlots as RawSlots, appContext, once, - parentInstance, ) - // set currentSlotConsumer to null to avoid affecting the child components - const prevSlotConsumer = setCurrentSlotConsumer(null) + // reset currentSlotOwner to null to avoid affecting the child components + const prevSlotOwner = setCurrentSlotOwner(null) // HMR if (__DEV__) { @@ -347,12 +346,12 @@ export function createComponent( endMeasure(instance, 'init') } - if (__FEATURE_SUSPENSE__ && parentInstance && parentInstance.suspense) { + if (__FEATURE_SUSPENSE__ && currentInstance && currentInstance.suspense) { setParentSuspense(prevSuspense) } - // restore currentSlotConsumer to previous value after setupFn is called - setCurrentSlotConsumer(prevSlotConsumer) + // restore currentSlotOwner to previous value after setupFn is called + setCurrentSlotOwner(prevSlotOwner) onScopeDispose(() => unmountComponent(instance), true) if (_insertionParent || isHydrating) { @@ -594,19 +593,19 @@ export class VaporComponentInstance implements GenericComponentInstance { rawSlots?: RawSlots | null, appContext?: GenericAppContext, once?: boolean, - parent: GenericComponentInstance | null = currentInstance, ) { this.vapor = true this.uid = nextUid() this.type = comp - this.parent = parent - this.root = parent ? parent.root : this + this.parent = currentInstance - if (parent) { - this.appContext = parent.appContext - this.provides = parent.provides - this.ids = parent.ids + if (currentInstance) { + this.root = currentInstance.root + this.appContext = currentInstance.appContext + this.provides = currentInstance.provides + this.ids = currentInstance.ids } else { + this.root = this this.appContext = appContext || emptyContext this.provides = Object.create(this.appContext.provides) this.ids = ['', 0, 0] @@ -655,7 +654,10 @@ export class VaporComponentInstance implements GenericComponentInstance { : rawSlots : EMPTY_OBJ - this.scopeId = currentInstance && currentInstance.type.__scopeId + // Use currentSlotOwner for scopeId inheritance when inside a slot + // This ensures components created in slots inherit the slot owner's scopeId + const scopeOwner = currentSlotOwner || currentInstance + this.scopeId = scopeOwner && scopeOwner.type.__scopeId // apply custom element special handling if (comp.ce) { @@ -745,7 +747,9 @@ export function createPlainElement( ;(el as any).$root = isSingleRoot if (!isHydrating) { - const scopeId = currentInstance!.type.__scopeId + // Use currentSlotOwner for scopeId when inside a slot + const scopeOwner = currentSlotOwner || currentInstance + const scopeId = scopeOwner!.type.__scopeId if (scopeId) setScopeId(el, [scopeId]) } diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts index b450e077a1..273f2569dd 100644 --- a/packages/runtime-vapor/src/componentSlots.ts +++ b/packages/runtime-vapor/src/componentSlots.ts @@ -6,7 +6,6 @@ import { currentInstance, isAsyncWrapper, isRef, - setCurrentInstance, } from '@vue/runtime-dom' import type { LooseRawProps, VaporComponentInstance } from './component' import { renderEffect } from './renderEffect' @@ -124,42 +123,47 @@ export function getSlot( } } -export let currentSlotConsumer: GenericComponentInstance | null = null +/** + * Tracks the slot owner (the component that defines the slot content). + * This is used for: + * 1. Getting the correct rawSlots in forwarded slots (via createSlot) + * 2. Inheriting the slot owner's scopeId + */ +export let currentSlotOwner: VaporComponentInstance | null = null -export function setCurrentSlotConsumer( - consumer: GenericComponentInstance | null, -): GenericComponentInstance | null { +export function setCurrentSlotOwner( + owner: VaporComponentInstance | null, +): VaporComponentInstance | null { try { - return currentSlotConsumer + return currentSlotOwner } finally { - currentSlotConsumer = consumer + currentSlotOwner = owner } } /** - * use currentSlotConsumer as parent, the currentSlotConsumer will be reset to null - * before setupFn call to avoid affecting children and restore to previous value - * after setupFn is called + * Get the effective slot instance for accessing rawSlots and scopeId. + * Prefers currentSlotOwner (if inside a slot), falls back to currentInstance. */ -export function getParentInstance(): GenericComponentInstance | null { - return currentSlotConsumer || currentInstance +export function getSlotInstance(): VaporComponentInstance { + return (currentSlotOwner || currentInstance) as VaporComponentInstance } /** - * Wrap a slot function to memoize currentInstance - * 1. ensure correct currentInstance in forwarded slots - * 2. elements created in the slot inherit the slot owner's scopeId + * Wrap a slot function to track the slot owner. + * + * This ensures: + * 1. createSlot gets rawSlots from the correct component (slot owner) + * 2. Elements inherit the slot owner's scopeId */ export function withVaporCtx(fn: Function): BlockFn { - const owner = currentInstance + const owner = currentInstance as VaporComponentInstance return (...args: any[]) => { - const prev = setCurrentInstance(owner) - const prevConsumer = setCurrentSlotConsumer(prev[0]) + const prevOwner = setCurrentSlotOwner(owner) try { return fn(...args) } finally { - setCurrentInstance(...prev) - setCurrentSlotConsumer(prevConsumer) + setCurrentSlotOwner(prevOwner) } } } @@ -176,7 +180,8 @@ export function createSlot( const _isLastInsertion = isLastInsertion if (!isHydrating) resetInsertionState() - const instance = currentInstance as VaporComponentInstance + // Use slot owner if inside a slot (forwarded slots), otherwise use currentInstance + const instance = getSlotInstance() const rawSlots = instance.rawSlots const slotProps = rawProps ? new Proxy(rawProps, rawPropsProxyHandlers) diff --git a/packages/runtime-vapor/src/components/Teleport.ts b/packages/runtime-vapor/src/components/Teleport.ts index f83da4f8bd..9379cdabdb 100644 --- a/packages/runtime-vapor/src/components/Teleport.ts +++ b/packages/runtime-vapor/src/components/Teleport.ts @@ -3,6 +3,7 @@ import { MismatchTypes, type TeleportProps, type TeleportTargetElement, + currentInstance, isMismatchAllowed, isTeleportDeferred, isTeleportDisabled, @@ -31,7 +32,6 @@ import { setCurrentHydrationNode, } from '../dom/hydration' import { applyTransitionHooks } from './Transition' -import { getParentInstance } from '../componentSlots' export const VaporTeleportImpl = { name: 'VaporTeleport', @@ -62,7 +62,7 @@ export class TeleportFragment extends VaporFragment { super([]) this.rawProps = props this.rawSlots = slots - this.parentComponent = getParentInstance() + this.parentComponent = currentInstance this.anchor = isHydrating ? undefined : __DEV__ diff --git a/packages/runtime-vapor/src/fragment.ts b/packages/runtime-vapor/src/fragment.ts index ed0b64e424..2c3f5554a3 100644 --- a/packages/runtime-vapor/src/fragment.ts +++ b/packages/runtime-vapor/src/fragment.ts @@ -14,6 +14,7 @@ import { type GenericComponentInstance, type TransitionHooks, type VNode, + currentInstance, queuePostFlushCb, setCurrentInstance, warnExtraneousAttributes, @@ -31,7 +32,6 @@ import { locateFragmentEndAnchor, locateHydrationNode, } from './dom/hydration' -import { getParentInstance } from './componentSlots' import { isArray } from '@vue/shared' import { renderEffect } from './renderEffect' @@ -99,7 +99,7 @@ export class DynamicFragment extends VaporFragment { constructor(anchorLabel?: string) { super([]) - this.parentComponent = getParentInstance() + this.parentComponent = currentInstance if (isHydrating) { this.anchorLabel = anchorLabel locateHydrationNode() diff --git a/packages/runtime-vapor/src/vdomInterop.ts b/packages/runtime-vapor/src/vdomInterop.ts index 60b5aad1f4..64ece00d88 100644 --- a/packages/runtime-vapor/src/vdomInterop.ts +++ b/packages/runtime-vapor/src/vdomInterop.ts @@ -60,7 +60,7 @@ import { } from '@vue/shared' import { type RawProps, rawPropsProxyHandlers } from './componentProps' import type { RawSlots, VaporSlot } from './componentSlots' -import { currentSlotScopeIds } from './componentSlots' +import { currentSlotScopeIds, getSlotInstance } from './componentSlots' import { renderEffect } from './renderEffect' import { _next, createTextNode } from './dom/node' import { optimizePropertyLookup } from './dom/prop' @@ -304,7 +304,6 @@ function createVDOMComponent( rawSlots as RawSlots, parentComponent ? parentComponent.appContext : undefined, undefined, - parentComponent, ) // overwrite how the vdom instance handles props @@ -351,7 +350,9 @@ function createVDOMComponent( frag.nodes = vnode.el as any } - vnode.scopeId = (currentInstance && currentInstance.type.__scopeId) || null + // Use currentSlotOwner for scopeId when inside a slot + const scopeOwner = getSlotInstance() + vnode.scopeId = (scopeOwner && scopeOwner.type.__scopeId) || null vnode.slotScopeIds = currentSlotScopeIds frag.insert = (parentNode, anchor, transition) => {