Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6a61f44

Browse files
authored
fix(teleport): don't move teleport children if not mounted (#14702)
close #14701
1 parent e7659be commit 6a61f44

2 files changed

Lines changed: 46 additions & 3 deletions

File tree

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2570,6 +2570,44 @@ describe('Suspense', () => {
25702570
expect(serializeInner(target)).toBe(``)
25712571
})
25722572

2573+
// #14701
2574+
test('should not crash when moving disabled teleport with component children inside suspense', async () => {
2575+
const target = nodeOps.createElement('div')
2576+
2577+
const Comp = {
2578+
render() {
2579+
return h('div', 'comp')
2580+
},
2581+
}
2582+
2583+
const Async = defineAsyncComponent({
2584+
render() {
2585+
// Multi-root fragment: element + disabled teleport with component child
2586+
return [
2587+
h('div', 'content'),
2588+
h(Teleport, { to: target, disabled: true }, h(Comp)),
2589+
]
2590+
},
2591+
})
2592+
2593+
const root = nodeOps.createElement('div')
2594+
render(
2595+
h(Suspense, null, {
2596+
default: h(Async),
2597+
fallback: h('div', 'fallback'),
2598+
}),
2599+
root,
2600+
)
2601+
expect(serializeInner(root)).toBe(`<div>fallback</div>`)
2602+
2603+
await Promise.all(deps)
2604+
await nextTick()
2605+
await nextTick()
2606+
expect(serializeInner(root)).toBe(
2607+
`<div>content</div><!--teleport start--><div>comp</div><!--teleport end-->`,
2608+
)
2609+
})
2610+
25732611
//#11617
25742612
test('update async component before resolve then update again', async () => {
25752613
const arr: boolean[] = []

‎packages/runtime-core/src/components/Teleport.ts‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export const TeleportImpl = {
9494
mc: mountChildren,
9595
pc: patchChildren,
9696
pbc: patchBlockChildren,
97-
o: { insert, querySelector, createText, createComment },
97+
o: { insert, querySelector, createText, createComment, parentNode },
9898
} = internals
9999

100100
const disabled = isTeleportDisabled(n2.props)
@@ -162,7 +162,11 @@ export const TeleportImpl = {
162162
if (pendingMounts.get(vnode) !== mountJob) return
163163
pendingMounts.delete(vnode)
164164
if (isTeleportDisabled(vnode.props)) {
165-
mount(vnode, container, vnode.anchor!)
165+
// Use the current parent of the placeholder instead of the
166+
// captured `container`, which may be stale if Suspense has moved
167+
// the branch to a different container during resolve.
168+
const mountContainer = parentNode(vnode.el!) || container
169+
mount(vnode, mountContainer, vnode.anchor!)
166170
updateCssVars(vnode, true)
167171
}
168172
mountToTarget(vnode)
@@ -389,7 +393,8 @@ function moveTeleport(
389393
// if this is a re-order and teleport is enabled (content is in target)
390394
// do not move children. So the opposite is: only move children if this
391395
// is not a reorder, or the teleport is disabled
392-
if (!isReorder || isTeleportDisabled(props)) {
396+
// #14701 don't move children if in pending mount
397+
if (!pendingMounts.has(vnode) && (!isReorder || isTeleportDisabled(props))) {
393398
// Teleport has either Array children or no children.
394399
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
395400
for (let i = 0; i < (children as VNode[]).length; i++) {

0 commit comments

Comments
 (0)