Преглед изворни кода

fix(runtime-vapor): preserve slot owner rendering context in resolveDynamicComponent (#14475)

close #14474
edison пре 2 месеци
родитељ
комит
a7795310f6

+ 4 - 0
packages/runtime-core/src/index.ts

@@ -688,3 +688,7 @@ export {
  * @internal
  */
 export { knownTemplateRefs, isTemplateRefKey } from './helpers/useTemplateRef'
+/**
+ * @internal
+ */
+export { setCurrentRenderingInstance } from './componentRenderContext'

+ 47 - 0
packages/runtime-vapor/__tests__/apiCreateDynamicComponent.spec.ts

@@ -1,13 +1,17 @@
 import { ref, shallowRef } from '@vue/reactivity'
 import { nextTick, resolveDynamicComponent } from '@vue/runtime-dom'
 import {
+  createComponent,
   createComponentWithFallback,
   createDynamicComponent,
+  createSlot,
   defineVaporComponent,
+  insert,
   renderEffect,
   setHtml,
   setInsertionState,
   template,
+  withVaporCtx,
 } from '../src'
 import { makeRender } from './_utils'
 
@@ -180,6 +184,49 @@ describe('api: createDynamicComponent', () => {
     )
   })
 
+  test('resolves slot owner local components after dynamic updates', async () => {
+    const current = shallowRef('Foo')
+    const Foo = defineVaporComponent({
+      setup() {
+        return template('<span>foo</span>')()
+      },
+    })
+    const Child = defineVaporComponent({
+      setup() {
+        const n0 = template('<section></section>')()
+        insert(createSlot('default'), n0 as ParentNode)
+        return n0
+      },
+    })
+
+    const { html } = define({
+      components: { Foo },
+      setup() {
+        return createComponent(Child, null, {
+          default: withVaporCtx(() =>
+            createDynamicComponent(() => current.value),
+          ),
+        })
+      },
+    }).render()
+
+    expect(html()).toBe(
+      '<section><span>foo</span><!--dynamic-component--><!--slot--></section>',
+    )
+
+    current.value = 'div'
+    await nextTick()
+    expect(html()).toBe(
+      '<section><div></div><!--dynamic-component--><!--slot--></section>',
+    )
+
+    current.value = 'Foo'
+    await nextTick()
+    expect(html()).toBe(
+      '<section><span>foo</span><!--dynamic-component--><!--slot--></section>',
+    )
+  })
+
   test('accept blocks', async () => {
     const { html } = define({
       setup() {

+ 21 - 3
packages/runtime-vapor/src/apiCreateDynamicComponent.ts

@@ -1,14 +1,20 @@
 import {
+  type ComponentInternalInstance,
   currentInstance,
   isKeepAlive,
   isVNode,
   resolveDynamicComponent,
+  setCurrentRenderingInstance,
 } from '@vue/runtime-dom'
 import { insert, isBlock } from './block'
-import { createComponentWithFallback, emptyContext } from './component'
+import {
+  type VaporComponentInstance,
+  createComponentWithFallback,
+  emptyContext,
+} from './component'
 import { renderEffect } from './renderEffect'
 import type { RawProps } from './componentProps'
-import type { RawSlots } from './componentSlots'
+import { type RawSlots, getScopeOwner } from './componentSlots'
 import {
   insertionAnchor,
   insertionParent,
@@ -37,6 +43,7 @@ export function createDynamicComponent(
       ? new DynamicFragment('dynamic-component')
       : new DynamicFragment()
 
+  const scopeOwner = getScopeOwner()
   const renderFn = () => {
     const value = getter()
     const appContext =
@@ -65,7 +72,7 @@ export function createDynamicComponent(
       }
 
       return createComponentWithFallback(
-        resolveDynamicComponent(value) as any,
+        withScopeOwner(scopeOwner, () => resolveDynamicComponent(value)),
         rawProps,
         rawSlots,
         isSingleRoot,
@@ -87,3 +94,14 @@ export function createDynamicComponent(
   }
   return frag
 }
+
+function withScopeOwner(owner: VaporComponentInstance | null, fn: () => any) {
+  const prev = setCurrentRenderingInstance(
+    owner as ComponentInternalInstance | null,
+  )
+  try {
+    return fn()
+  } finally {
+    setCurrentRenderingInstance(prev)
+  }
+}