From ea03d8e5aca880551dfffd7949664f650f3cb973 Mon Sep 17 00:00:00 2001 From: rzzf Date: Sun, 7 Dec 2025 02:03:10 +0800 Subject: [PATCH 1/2] fix(runtime-core): correctly propagate placeholder for async component --- .../__tests__/components/Suspense.spec.ts | 63 +++++++++++++++++++ .../runtime-core/src/componentRenderUtils.ts | 3 + packages/runtime-core/src/renderer.ts | 1 + 3 files changed, 67 insertions(+) diff --git a/packages/runtime-core/__tests__/components/Suspense.spec.ts b/packages/runtime-core/__tests__/components/Suspense.spec.ts index 4e8da3288f1..9e05e6f80be 100644 --- a/packages/runtime-core/__tests__/components/Suspense.spec.ts +++ b/packages/runtime-core/__tests__/components/Suspense.spec.ts @@ -2395,4 +2395,67 @@ describe('Suspense', () => { expect(unmounted).toHaveBeenCalledTimes(1) }) }) + + // #14173 + test('renders multiple async components in Suspense with v-for and updates list order with HOC', async () => { + const CompAsyncSetup = defineAsyncComponent({ + props: ['item'], + render(ctx: any) { + return h('div', ctx.item.name) + }, + }) + + const CompWrapper = { + props: ['item'], + render(ctx: any) { + return h(CompAsyncSetup, { item: ctx.item }) + }, + } + + const items = ref([ + { id: 1, name: '111' }, + { id: 2, name: '222' }, + { id: 3, name: '333' }, + ]) + + const Comp = { + setup() { + return () => + h(Suspense, null, { + default: () => + h('div', [ + h( + Fragment, + null, + items.value.map(item => + h(CompWrapper, { item, key: item.id }), + ), + ), + ]), + }) + }, + } + + const root = nodeOps.createElement('div') + render(h(Comp), root) + + await nextTick() + await Promise.all(deps) + + expect(serializeInner(root)).toBe( + `
111
222
333
`, + ) + + items.value = [ + { id: 4, name: '444' }, + { id: 5, name: '555' }, + { id: 6, name: '666' }, + ] + + await nextTick() + await Promise.all(deps) + expect(serializeInner(root)).toBe( + `
444
555
666
`, + ) + }) }) diff --git a/packages/runtime-core/src/componentRenderUtils.ts b/packages/runtime-core/src/componentRenderUtils.ts index a1afae6201a..fa100b54df1 100644 --- a/packages/runtime-core/src/componentRenderUtils.ts +++ b/packages/runtime-core/src/componentRenderUtils.ts @@ -461,6 +461,9 @@ export function updateHOCHostEl( } if (root === vnode) { ;(vnode = parent.vnode).el = el + if (root.placeholder) { + vnode.placeholder = root.placeholder + } parent = parent.parent } else { break diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 192bb44474e..c3fdf8c0f0f 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1239,6 +1239,7 @@ function baseCreateRenderer( const placeholder = (instance.subTree = createVNode(Comment)) processCommentNode(null, placeholder, container!, anchor) initialVNode.placeholder = placeholder.el + updateHOCHostEl(instance, placeholder.el) } } else { setupRenderEffect( From ac75959e9f3daab7b2faaacd70e5469b9761558b Mon Sep 17 00:00:00 2001 From: rzzf Date: Mon, 8 Dec 2025 10:47:28 +0800 Subject: [PATCH 2/2] fix: update --- packages/runtime-core/src/componentRenderUtils.ts | 3 --- packages/runtime-core/src/renderer.ts | 13 +++++++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/runtime-core/src/componentRenderUtils.ts b/packages/runtime-core/src/componentRenderUtils.ts index fa100b54df1..a1afae6201a 100644 --- a/packages/runtime-core/src/componentRenderUtils.ts +++ b/packages/runtime-core/src/componentRenderUtils.ts @@ -461,9 +461,6 @@ export function updateHOCHostEl( } if (root === vnode) { ;(vnode = parent.vnode).el = el - if (root.placeholder) { - vnode.placeholder = root.placeholder - } parent = parent.parent } else { break diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index c3fdf8c0f0f..bac0c317741 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1239,7 +1239,6 @@ function baseCreateRenderer( const placeholder = (instance.subTree = createVNode(Comment)) processCommentNode(null, placeholder, container!, anchor) initialVNode.placeholder = placeholder.el - updateHOCHostEl(instance, placeholder.el) } } else { setupRenderEffect( @@ -1997,7 +1996,9 @@ function baseCreateRenderer( const anchor = nextIndex + 1 < l2 ? // #13559, fallback to el placeholder for unresolved async component - anchorVNode.el || anchorVNode.placeholder + anchorVNode.el || + anchorVNode.placeholder || + getSubTreeElement(anchorVNode) : parentAnchor if (newIndexToOldIndexMap[i] === 0) { // mount new @@ -2419,6 +2420,14 @@ function baseCreateRenderer( ) } + const getSubTreeElement = (vnode: VNode): RendererNode | null => { + let current = vnode + while (current.component) { + current = current.component.subTree + } + return current.el || current.placeholder + } + return { render, hydrate,