Просмотр исходного кода

refactor(runtime-vapor): use dedicated currentSlotOwner instead of changing currentInstance in withVaporCtx

daiwei 6 месяцев назад
Родитель
Сommit
7e0d8130e6

+ 34 - 30
packages/runtime-vapor/src/component.ts

@@ -77,10 +77,10 @@ import {
   type RawSlots,
   type RawSlots,
   type StaticSlots,
   type StaticSlots,
   type VaporSlot,
   type VaporSlot,
+  currentSlotOwner,
   dynamicSlotsProxyHandlers,
   dynamicSlotsProxyHandlers,
-  getParentInstance,
   getSlot,
   getSlot,
-  setCurrentSlotConsumer,
+  setCurrentSlotOwner,
 } from './componentSlots'
 } from './componentSlots'
 import { hmrReload, hmrRerender } from './hmr'
 import { hmrReload, hmrRerender } from './hmr'
 import {
 import {
@@ -202,24 +202,22 @@ export function createComponent(
     resetInsertionState()
     resetInsertionState()
   }
   }
 
 
-  const parentInstance = getParentInstance()
-
   let prevSuspense: SuspenseBoundary | null = null
   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 (
   if (
     (isSingleRoot ||
     (isSingleRoot ||
       // transition has attrs fallthrough
       // transition has attrs fallthrough
-      (parentInstance && isVaporTransition(parentInstance!.type))) &&
+      (currentInstance && isVaporTransition(currentInstance!.type))) &&
     component.inheritAttrs !== false &&
     component.inheritAttrs !== false &&
-    isVaporComponent(parentInstance) &&
-    parentInstance.hasFallthrough
+    isVaporComponent(currentInstance) &&
+    currentInstance.hasFallthrough
   ) {
   ) {
     // check if we are the single root of the parent
     // check if we are the single root of the parent
     // if yes, inject parent attrs as dynamic props source
     // if yes, inject parent attrs as dynamic props source
-    const attrs = parentInstance.attrs
+    const attrs = currentInstance.attrs
     if (rawProps && rawProps !== EMPTY_OBJ) {
     if (rawProps && rawProps !== EMPTY_OBJ) {
       ;((rawProps as RawProps).$ || ((rawProps as RawProps).$ = [])).push(
       ;((rawProps as RawProps).$ || ((rawProps as RawProps).$ = [])).push(
         () => attrs,
         () => attrs,
@@ -230,8 +228,12 @@ export function createComponent(
   }
   }
 
 
   // keep-alive
   // 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,
       component,
     )
     )
     // @ts-expect-error
     // @ts-expect-error
@@ -240,14 +242,12 @@ export function createComponent(
 
 
   // vdom interop enabled and component is not an explicit vapor component
   // vdom interop enabled and component is not an explicit vapor component
   if (appContext.vapor && !component.__vapor) {
   if (appContext.vapor && !component.__vapor) {
-    const prevSlotConsumer = setCurrentSlotConsumer(null)
     const frag = appContext.vapor.vdomMount(
     const frag = appContext.vapor.vdomMount(
       component as any,
       component as any,
-      parentInstance as any,
+      currentInstance as any,
       rawProps,
       rawProps,
       rawSlots,
       rawSlots,
     )
     )
-    setCurrentSlotConsumer(prevSlotConsumer)
     if (!isHydrating) {
     if (!isHydrating) {
       if (_insertionParent) insert(frag, _insertionParent, _insertionAnchor)
       if (_insertionParent) insert(frag, _insertionParent, _insertionAnchor)
     } else {
     } else {
@@ -280,11 +280,10 @@ export function createComponent(
     rawSlots as RawSlots,
     rawSlots as RawSlots,
     appContext,
     appContext,
     once,
     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
   // HMR
   if (__DEV__) {
   if (__DEV__) {
@@ -347,12 +346,12 @@ export function createComponent(
     endMeasure(instance, 'init')
     endMeasure(instance, 'init')
   }
   }
 
 
-  if (__FEATURE_SUSPENSE__ && parentInstance && parentInstance.suspense) {
+  if (__FEATURE_SUSPENSE__ && currentInstance && currentInstance.suspense) {
     setParentSuspense(prevSuspense)
     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)
   onScopeDispose(() => unmountComponent(instance), true)
 
 
   if (_insertionParent || isHydrating) {
   if (_insertionParent || isHydrating) {
@@ -594,19 +593,19 @@ export class VaporComponentInstance implements GenericComponentInstance {
     rawSlots?: RawSlots | null,
     rawSlots?: RawSlots | null,
     appContext?: GenericAppContext,
     appContext?: GenericAppContext,
     once?: boolean,
     once?: boolean,
-    parent: GenericComponentInstance | null = currentInstance,
   ) {
   ) {
     this.vapor = true
     this.vapor = true
     this.uid = nextUid()
     this.uid = nextUid()
     this.type = comp
     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 {
     } else {
+      this.root = this
       this.appContext = appContext || emptyContext
       this.appContext = appContext || emptyContext
       this.provides = Object.create(this.appContext.provides)
       this.provides = Object.create(this.appContext.provides)
       this.ids = ['', 0, 0]
       this.ids = ['', 0, 0]
@@ -655,7 +654,10 @@ export class VaporComponentInstance implements GenericComponentInstance {
         : rawSlots
         : rawSlots
       : EMPTY_OBJ
       : 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
     // apply custom element special handling
     if (comp.ce) {
     if (comp.ce) {
@@ -745,7 +747,9 @@ export function createPlainElement(
   ;(el as any).$root = isSingleRoot
   ;(el as any).$root = isSingleRoot
 
 
   if (!isHydrating) {
   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])
     if (scopeId) setScopeId(el, [scopeId])
   }
   }
 
 

+ 26 - 21
packages/runtime-vapor/src/componentSlots.ts

@@ -6,7 +6,6 @@ import {
   currentInstance,
   currentInstance,
   isAsyncWrapper,
   isAsyncWrapper,
   isRef,
   isRef,
-  setCurrentInstance,
 } from '@vue/runtime-dom'
 } from '@vue/runtime-dom'
 import type { LooseRawProps, VaporComponentInstance } from './component'
 import type { LooseRawProps, VaporComponentInstance } from './component'
 import { renderEffect } from './renderEffect'
 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 {
   try {
-    return currentSlotConsumer
+    return currentSlotOwner
   } finally {
   } 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 {
 export function withVaporCtx(fn: Function): BlockFn {
-  const owner = currentInstance
+  const owner = currentInstance as VaporComponentInstance
   return (...args: any[]) => {
   return (...args: any[]) => {
-    const prev = setCurrentInstance(owner)
-    const prevConsumer = setCurrentSlotConsumer(prev[0])
+    const prevOwner = setCurrentSlotOwner(owner)
     try {
     try {
       return fn(...args)
       return fn(...args)
     } finally {
     } finally {
-      setCurrentInstance(...prev)
-      setCurrentSlotConsumer(prevConsumer)
+      setCurrentSlotOwner(prevOwner)
     }
     }
   }
   }
 }
 }
@@ -176,7 +180,8 @@ export function createSlot(
   const _isLastInsertion = isLastInsertion
   const _isLastInsertion = isLastInsertion
   if (!isHydrating) resetInsertionState()
   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 rawSlots = instance.rawSlots
   const slotProps = rawProps
   const slotProps = rawProps
     ? new Proxy(rawProps, rawPropsProxyHandlers)
     ? new Proxy(rawProps, rawPropsProxyHandlers)

+ 2 - 2
packages/runtime-vapor/src/components/Teleport.ts

@@ -3,6 +3,7 @@ import {
   MismatchTypes,
   MismatchTypes,
   type TeleportProps,
   type TeleportProps,
   type TeleportTargetElement,
   type TeleportTargetElement,
+  currentInstance,
   isMismatchAllowed,
   isMismatchAllowed,
   isTeleportDeferred,
   isTeleportDeferred,
   isTeleportDisabled,
   isTeleportDisabled,
@@ -31,7 +32,6 @@ import {
   setCurrentHydrationNode,
   setCurrentHydrationNode,
 } from '../dom/hydration'
 } from '../dom/hydration'
 import { applyTransitionHooks } from './Transition'
 import { applyTransitionHooks } from './Transition'
-import { getParentInstance } from '../componentSlots'
 
 
 export const VaporTeleportImpl = {
 export const VaporTeleportImpl = {
   name: 'VaporTeleport',
   name: 'VaporTeleport',
@@ -62,7 +62,7 @@ export class TeleportFragment extends VaporFragment {
     super([])
     super([])
     this.rawProps = props
     this.rawProps = props
     this.rawSlots = slots
     this.rawSlots = slots
-    this.parentComponent = getParentInstance()
+    this.parentComponent = currentInstance
     this.anchor = isHydrating
     this.anchor = isHydrating
       ? undefined
       ? undefined
       : __DEV__
       : __DEV__

+ 2 - 2
packages/runtime-vapor/src/fragment.ts

@@ -14,6 +14,7 @@ import {
   type GenericComponentInstance,
   type GenericComponentInstance,
   type TransitionHooks,
   type TransitionHooks,
   type VNode,
   type VNode,
+  currentInstance,
   queuePostFlushCb,
   queuePostFlushCb,
   setCurrentInstance,
   setCurrentInstance,
   warnExtraneousAttributes,
   warnExtraneousAttributes,
@@ -31,7 +32,6 @@ import {
   locateFragmentEndAnchor,
   locateFragmentEndAnchor,
   locateHydrationNode,
   locateHydrationNode,
 } from './dom/hydration'
 } from './dom/hydration'
-import { getParentInstance } from './componentSlots'
 import { isArray } from '@vue/shared'
 import { isArray } from '@vue/shared'
 import { renderEffect } from './renderEffect'
 import { renderEffect } from './renderEffect'
 
 
@@ -99,7 +99,7 @@ export class DynamicFragment extends VaporFragment {
 
 
   constructor(anchorLabel?: string) {
   constructor(anchorLabel?: string) {
     super([])
     super([])
-    this.parentComponent = getParentInstance()
+    this.parentComponent = currentInstance
     if (isHydrating) {
     if (isHydrating) {
       this.anchorLabel = anchorLabel
       this.anchorLabel = anchorLabel
       locateHydrationNode()
       locateHydrationNode()

+ 4 - 3
packages/runtime-vapor/src/vdomInterop.ts

@@ -60,7 +60,7 @@ import {
 } from '@vue/shared'
 } from '@vue/shared'
 import { type RawProps, rawPropsProxyHandlers } from './componentProps'
 import { type RawProps, rawPropsProxyHandlers } from './componentProps'
 import type { RawSlots, VaporSlot } from './componentSlots'
 import type { RawSlots, VaporSlot } from './componentSlots'
-import { currentSlotScopeIds } from './componentSlots'
+import { currentSlotScopeIds, getSlotInstance } from './componentSlots'
 import { renderEffect } from './renderEffect'
 import { renderEffect } from './renderEffect'
 import { _next, createTextNode } from './dom/node'
 import { _next, createTextNode } from './dom/node'
 import { optimizePropertyLookup } from './dom/prop'
 import { optimizePropertyLookup } from './dom/prop'
@@ -304,7 +304,6 @@ function createVDOMComponent(
     rawSlots as RawSlots,
     rawSlots as RawSlots,
     parentComponent ? parentComponent.appContext : undefined,
     parentComponent ? parentComponent.appContext : undefined,
     undefined,
     undefined,
-    parentComponent,
   )
   )
 
 
   // overwrite how the vdom instance handles props
   // overwrite how the vdom instance handles props
@@ -351,7 +350,9 @@ function createVDOMComponent(
     frag.nodes = vnode.el as any
     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
   vnode.slotScopeIds = currentSlotScopeIds
 
 
   frag.insert = (parentNode, anchor, transition) => {
   frag.insert = (parentNode, anchor, transition) => {