From 109d1d87798aa09ae2b0069467dad7090ca19e8c Mon Sep 17 00:00:00 2001 From: daiwei Date: Sat, 17 May 2025 23:04:04 +0800 Subject: [PATCH 1/2] fix(teleport): avoid infinite recursion in process when teleport is deferred --- packages/runtime-core/src/components/Teleport.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/runtime-core/src/components/Teleport.ts b/packages/runtime-core/src/components/Teleport.ts index fc2ee4c086f..0aca4668cf0 100644 --- a/packages/runtime-core/src/components/Teleport.ts +++ b/packages/runtime-core/src/components/Teleport.ts @@ -164,6 +164,7 @@ export const TeleportImpl = { } if (isTeleportDeferred(n2.props)) { + n2.el!.__isMounted = false queuePostRenderEffect(() => { mountToTarget() n2.el!.__isMounted = true @@ -172,7 +173,7 @@ export const TeleportImpl = { mountToTarget() } } else { - if (isTeleportDeferred(n2.props) && !n1.el!.__isMounted) { + if (isTeleportDeferred(n2.props) && n1.el!.__isMounted === false) { queuePostRenderEffect(() => { TeleportImpl.process( n1, From 034d1fcff819626d38cbef95180861e9c64f1473 Mon Sep 17 00:00:00 2001 From: daiwei Date: Sun, 18 May 2025 08:37:16 +0800 Subject: [PATCH 2/2] test: add test --- .../__tests__/components/Teleport.spec.ts | 57 +++++++++++++++++++ .../runtime-core/src/components/Teleport.ts | 3 +- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/__tests__/components/Teleport.spec.ts b/packages/runtime-core/__tests__/components/Teleport.spec.ts index 4c35b1f2d79..69a1c4cb25f 100644 --- a/packages/runtime-core/__tests__/components/Teleport.spec.ts +++ b/packages/runtime-core/__tests__/components/Teleport.spec.ts @@ -16,6 +16,7 @@ import { render, serialize, serializeInner, + useModel, withDirectives, } from '@vue/runtime-test' import { @@ -144,6 +145,62 @@ describe('renderer: teleport', () => { `"
Footer
bar
"`, ) }) + + // #13349 + test('handle deferred teleport updates before and after mount', async () => { + const root = document.createElement('div') + document.body.appendChild(root) + + const show = ref(false) + const data2 = ref('2') + const data3 = ref('3') + + const Comp = { + props: { + modelValue: {}, + modelModifiers: {}, + }, + emits: ['update:modelValue'], + setup(props: any) { + const data2 = useModel(props, 'modelValue') + data2.value = '2+' + return () => h('span') + }, + } + + createDOMApp({ + setup() { + setTimeout(() => (show.value = true), 5) + setTimeout(() => (data3.value = '3+'), 10) + }, + render() { + return h(Fragment, null, [ + h('span', { id: 'targetId001' }), + show.value + ? h(Fragment, null, [ + h(Teleport, { to: '#targetId001', defer: true }, [ + createTextVNode(String(data3.value)), + ]), + h(Comp, { + modelValue: data2.value, + 'onUpdate:modelValue': (event: any) => + (data2.value = event), + }), + ]) + : createCommentVNode('v-if'), + ]) + }, + }).mount(root) + + expect(root.innerHTML).toMatchInlineSnapshot( + `""`, + ) + + await new Promise(r => setTimeout(r, 10)) + expect(root.innerHTML).toMatchInlineSnapshot( + `"3+"`, + ) + }) }) function runSharedTests(deferMode: boolean) { diff --git a/packages/runtime-core/src/components/Teleport.ts b/packages/runtime-core/src/components/Teleport.ts index 0aca4668cf0..c37356a7869 100644 --- a/packages/runtime-core/src/components/Teleport.ts +++ b/packages/runtime-core/src/components/Teleport.ts @@ -167,7 +167,7 @@ export const TeleportImpl = { n2.el!.__isMounted = false queuePostRenderEffect(() => { mountToTarget() - n2.el!.__isMounted = true + delete n2.el!.__isMounted }, parentSuspense) } else { mountToTarget() @@ -187,7 +187,6 @@ export const TeleportImpl = { optimized, internals, ) - delete n1.el!.__isMounted }, parentSuspense) return }