fix(runtime-core): ensure correct anchor el for unresolved async components (#13560)

close #13559
This commit is contained in:
linzhe 2025-07-23 08:42:10 +08:00 committed by GitHub
parent 9b029239ed
commit 7f2994393d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 61 additions and 1 deletions

View File

@ -2230,5 +2230,57 @@ describe('Suspense', () => {
fallback: [h('div'), h('div')],
})
})
// #13559
test('renders multiple async components in Suspense with v-for and updates on items change', async () => {
const CompAsyncSetup = defineAsyncComponent({
props: ['item'],
render(ctx: any) {
return h('div', ctx.item.name)
},
})
const items = ref([
{ id: 1, name: '111' },
{ id: 2, name: '222' },
{ id: 3, name: '333' },
])
const Comp = {
setup() {
return () =>
h(Suspense, null, {
default: () =>
h(
Fragment,
null,
items.value.map(item =>
h(CompAsyncSetup, { item, key: item.id }),
),
),
})
},
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
await nextTick()
await Promise.all(deps)
expect(serializeInner(root)).toBe(
`<div>111</div><div>222</div><div>333</div>`,
)
items.value = [
{ id: 4, name: '444' },
{ id: 5, name: '555' },
{ id: 6, name: '666' },
]
await nextTick()
await Promise.all(deps)
expect(serializeInner(root)).toBe(
`<div>444</div><div>555</div><div>666</div>`,
)
})
})
})

View File

@ -1226,6 +1226,7 @@ function baseCreateRenderer(
if (!initialVNode.el) {
const placeholder = (instance.subTree = createVNode(Comment))
processCommentNode(null, placeholder, container!, anchor)
initialVNode.placeholder = placeholder.el
}
} else {
setupRenderEffect(
@ -1979,8 +1980,12 @@ function baseCreateRenderer(
for (i = toBePatched - 1; i >= 0; i--) {
const nextIndex = s2 + i
const nextChild = c2[nextIndex] as VNode
const anchorVNode = c2[nextIndex + 1] as VNode
const anchor =
nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor
nextIndex + 1 < l2
? // #13559, fallback to el placeholder for unresolved async component
anchorVNode.el || anchorVNode.placeholder
: parentAnchor
if (newIndexToOldIndexMap[i] === 0) {
// mount new
patch(

View File

@ -196,6 +196,7 @@ export interface VNode<
// DOM
el: HostNode | null
placeholder: HostNode | null // async component el placeholder
anchor: HostNode | null // fragment anchor
target: HostElement | null // teleport target
targetStart: HostNode | null // teleport target start anchor
@ -711,6 +712,8 @@ export function cloneVNode<T, U>(
suspense: vnode.suspense,
ssContent: vnode.ssContent && cloneVNode(vnode.ssContent),
ssFallback: vnode.ssFallback && cloneVNode(vnode.ssFallback),
placeholder: vnode.placeholder,
el: vnode.el,
anchor: vnode.anchor,
ctx: vnode.ctx,