diff --git a/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts b/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts
index 90e3e28b..82acc4bd 100644
--- a/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts
+++ b/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts
@@ -22,7 +22,8 @@ import {
SetupContext,
createApp,
FunctionalComponent,
- renderList
+ renderList,
+ onUnmounted
} from '@vue/runtime-test'
import { PatchFlags, SlotFlags } from '@vue/shared'
import { SuspenseImpl } from '../src/components/Suspense'
@@ -826,6 +827,55 @@ describe('renderer: optimized mode', () => {
expect(inner(root)).toBe('
')
})
+ // #4183
+ test('should not take unmount children fast path /w Suspense', async () => {
+ const show = ref(true)
+ const spyUnmounted = jest.fn()
+
+ const Parent = {
+ setup(props: any, { slots }: SetupContext) {
+ return () => (
+ openBlock(),
+ createBlock(SuspenseImpl, null, {
+ default: withCtx(() => [renderSlot(slots, 'default')]),
+ _: SlotFlags.FORWARDED
+ })
+ )
+ }
+ }
+
+ const Child = {
+ setup() {
+ onUnmounted(spyUnmounted)
+ return () => createVNode('div', null, show.value, PatchFlags.TEXT)
+ }
+ }
+
+ const app = createApp({
+ render() {
+ return show.value
+ ? (openBlock(),
+ createBlock(
+ Parent,
+ { key: 0 },
+ {
+ default: withCtx(() => [createVNode(Child)]),
+ _: SlotFlags.STABLE
+ }
+ ))
+ : createCommentVNode('v-if', true)
+ }
+ })
+
+ app.mount(root)
+ expect(inner(root)).toBe('true
')
+
+ show.value = false
+ await nextTick()
+ expect(inner(root)).toBe('')
+ expect(spyUnmounted).toHaveBeenCalledTimes(1)
+ })
+
// #3881
// root cause: fragment inside a compiled slot passed to component which
// programmatically invokes the slot. The entire slot should de-opt but
diff --git a/packages/runtime-core/src/components/Suspense.ts b/packages/runtime-core/src/components/Suspense.ts
index ffc90018..6a27b7c2 100644
--- a/packages/runtime-core/src/components/Suspense.ts
+++ b/packages/runtime-core/src/components/Suspense.ts
@@ -749,7 +749,7 @@ function normalizeSuspenseSlot(s: any) {
s = singleChild
}
s = normalizeVNode(s)
- if (block) {
+ if (block && !s.dynamicChildren) {
s.dynamicChildren = block.filter(c => c !== s)
}
return s