Skip to content

Commit f5b3bf2

Browse files
authored
fix(runtime-core): ensure correct anchor el for deeper unresolved async components (#14182)
close #14173
1 parent 945a543 commit f5b3bf2

File tree

2 files changed

+111
-2
lines changed

2 files changed

+111
-2
lines changed

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

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2360,6 +2360,101 @@ describe('Suspense', () => {
23602360
)
23612361
})
23622362

2363+
// #14173
2364+
test('nested async components with v-for + only Suspense and async component wrappers', async () => {
2365+
const CompAsyncSetup = defineAsyncComponent({
2366+
props: ['item', 'id'],
2367+
render(ctx: any) {
2368+
return h('div', ctx.id + '-' + ctx.item.name)
2369+
},
2370+
})
2371+
const items = ref([
2372+
{ id: 1, name: 'a' },
2373+
{ id: 2, name: 'b' },
2374+
{ id: 3, name: 'c' },
2375+
])
2376+
const Comp = {
2377+
props: ['id'],
2378+
setup(props: any) {
2379+
return () =>
2380+
h(Suspense, null, {
2381+
default: () =>
2382+
h(
2383+
Fragment,
2384+
null,
2385+
items.value.map(item =>
2386+
h(CompAsyncSetup, {
2387+
item,
2388+
key: item.id,
2389+
id: props.id,
2390+
}),
2391+
),
2392+
),
2393+
})
2394+
},
2395+
}
2396+
2397+
const CompAsyncWrapper = defineAsyncComponent({
2398+
props: ['id'],
2399+
render(ctx: any) {
2400+
return h(Comp, { id: ctx.id })
2401+
},
2402+
})
2403+
const CompWrapper = defineComponent({
2404+
props: ['id'],
2405+
render(ctx: any) {
2406+
return h(CompAsyncWrapper, { id: ctx.id })
2407+
},
2408+
})
2409+
const list = ref([{ id: 1 }, { id: 2 }, { id: 3 }])
2410+
2411+
const App = {
2412+
setup() {
2413+
return () =>
2414+
h(Suspense, null, {
2415+
default: () =>
2416+
h(
2417+
Fragment,
2418+
null,
2419+
list.value.map(item =>
2420+
h(CompWrapper, { id: item.id, key: item.id }),
2421+
),
2422+
),
2423+
})
2424+
},
2425+
}
2426+
2427+
const root = nodeOps.createElement('div')
2428+
render(h(App), root)
2429+
await nextTick()
2430+
await Promise.all(deps)
2431+
await Promise.all(deps)
2432+
2433+
expect(serializeInner(root)).toBe(
2434+
`<div>1-a</div><div>1-b</div><div>1-c</div><div>2-a</div><div>2-b</div><div>2-c</div><div>3-a</div><div>3-b</div><div>3-c</div>`,
2435+
)
2436+
2437+
list.value = [{ id: 4 }, { id: 5 }, { id: 6 }]
2438+
await nextTick()
2439+
await Promise.all(deps)
2440+
await Promise.all(deps)
2441+
expect(serializeInner(root)).toBe(
2442+
`<div>4-a</div><div>4-b</div><div>4-c</div><div>5-a</div><div>5-b</div><div>5-c</div><div>6-a</div><div>6-b</div><div>6-c</div>`,
2443+
)
2444+
2445+
items.value = [
2446+
{ id: 4, name: 'd' },
2447+
{ id: 5, name: 'f' },
2448+
{ id: 6, name: 'g' },
2449+
]
2450+
await nextTick()
2451+
await Promise.all(deps)
2452+
await Promise.all(deps)
2453+
expect(serializeInner(root)).toBe(
2454+
`<div>4-d</div><div>4-f</div><div>4-g</div><div>5-d</div><div>5-f</div><div>5-g</div><div>6-d</div><div>6-f</div><div>6-g</div>`,
2455+
)
2456+
})
2457+
23632458
test('should call unmounted directive once when fallback is replaced by resolved async component', async () => {
23642459
const Comp = {
23652460
render() {

packages/runtime-core/src/renderer.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1995,8 +1995,8 @@ function baseCreateRenderer(
19951995
const anchorVNode = c2[nextIndex + 1] as VNode
19961996
const anchor =
19971997
nextIndex + 1 < l2
1998-
? // #13559, fallback to el placeholder for unresolved async component
1999-
anchorVNode.el || anchorVNode.placeholder
1998+
? // #13559, #14173 fallback to el placeholder for unresolved async component
1999+
anchorVNode.el || resolveAsyncComponentPlaceholder(anchorVNode)
20002000
: parentAnchor
20012001
if (newIndexToOldIndexMap[i] === 0) {
20022002
// mount new
@@ -2577,3 +2577,17 @@ export function invalidateMount(hooks: LifecycleHook): void {
25772577
hooks[i].flags! |= SchedulerJobFlags.DISPOSED
25782578
}
25792579
}
2580+
2581+
function resolveAsyncComponentPlaceholder(anchorVnode: VNode) {
2582+
if (anchorVnode.placeholder) {
2583+
return anchorVnode.placeholder
2584+
}
2585+
2586+
// anchor vnode maybe is a wrapper component has single unresolved async component
2587+
const instance = anchorVnode.component
2588+
if (instance) {
2589+
return resolveAsyncComponentPlaceholder(instance.subTree)
2590+
}
2591+
2592+
return null
2593+
}

0 commit comments

Comments
 (0)