Skip to content

Commit 7f29943

Browse files
authored
fix(runtime-core): ensure correct anchor el for unresolved async components (#13560)
close #13559
1 parent 9b02923 commit 7f29943

File tree

3 files changed

+61
-1
lines changed

3 files changed

+61
-1
lines changed

‎packages/runtime-core/__tests__/components/Suspense.spec.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2230,5 +2230,57 @@ describe('Suspense', () => {
22302230
fallback: [h('div'), h('div')],
22312231
})
22322232
})
2233+
2234+
// #13559
2235+
test('renders multiple async components in Suspense with v-for and updates on items change', async () => {
2236+
const CompAsyncSetup = defineAsyncComponent({
2237+
props: ['item'],
2238+
render(ctx: any) {
2239+
return h('div', ctx.item.name)
2240+
},
2241+
})
2242+
2243+
const items = ref([
2244+
{ id: 1, name: '111' },
2245+
{ id: 2, name: '222' },
2246+
{ id: 3, name: '333' },
2247+
])
2248+
2249+
const Comp = {
2250+
setup() {
2251+
return () =>
2252+
h(Suspense, null, {
2253+
default: () =>
2254+
h(
2255+
Fragment,
2256+
null,
2257+
items.value.map(item =>
2258+
h(CompAsyncSetup, { item, key: item.id }),
2259+
),
2260+
),
2261+
})
2262+
},
2263+
}
2264+
2265+
const root = nodeOps.createElement('div')
2266+
render(h(Comp), root)
2267+
await nextTick()
2268+
await Promise.all(deps)
2269+
2270+
expect(serializeInner(root)).toBe(
2271+
`<div>111</div><div>222</div><div>333</div>`,
2272+
)
2273+
2274+
items.value = [
2275+
{ id: 4, name: '444' },
2276+
{ id: 5, name: '555' },
2277+
{ id: 6, name: '666' },
2278+
]
2279+
await nextTick()
2280+
await Promise.all(deps)
2281+
expect(serializeInner(root)).toBe(
2282+
`<div>444</div><div>555</div><div>666</div>`,
2283+
)
2284+
})
22332285
})
22342286
})

‎packages/runtime-core/src/renderer.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1226,6 +1226,7 @@ function baseCreateRenderer(
12261226
if (!initialVNode.el) {
12271227
const placeholder = (instance.subTree = createVNode(Comment))
12281228
processCommentNode(null, placeholder, container!, anchor)
1229+
initialVNode.placeholder = placeholder.el
12291230
}
12301231
} else {
12311232
setupRenderEffect(
@@ -1979,8 +1980,12 @@ function baseCreateRenderer(
19791980
for (i = toBePatched - 1; i >= 0; i--) {
19801981
const nextIndex = s2 + i
19811982
const nextChild = c2[nextIndex] as VNode
1983+
const anchorVNode = c2[nextIndex + 1] as VNode
19821984
const anchor =
1983-
nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor
1985+
nextIndex + 1 < l2
1986+
? // #13559, fallback to el placeholder for unresolved async component
1987+
anchorVNode.el || anchorVNode.placeholder
1988+
: parentAnchor
19841989
if (newIndexToOldIndexMap[i] === 0) {
19851990
// mount new
19861991
patch(

‎packages/runtime-core/src/vnode.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ export interface VNode<
196196

197197
// DOM
198198
el: HostNode | null
199+
placeholder: HostNode | null // async component el placeholder
199200
anchor: HostNode | null // fragment anchor
200201
target: HostElement | null // teleport target
201202
targetStart: HostNode | null // teleport target start anchor
@@ -711,6 +712,8 @@ export function cloneVNode<T, U>(
711712
suspense: vnode.suspense,
712713
ssContent: vnode.ssContent && cloneVNode(vnode.ssContent),
713714
ssFallback: vnode.ssFallback && cloneVNode(vnode.ssFallback),
715+
placeholder: vnode.placeholder,
716+
714717
el: vnode.el,
715718
anchor: vnode.anchor,
716719
ctx: vnode.ctx,

0 commit comments

Comments
 (0)