diff --git a/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts b/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts
index 73e566c9..8b674b8a 100644
--- a/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts
+++ b/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts
@@ -14,7 +14,8 @@ import {
nextTick,
defineComponent,
withCtx,
- renderSlot
+ renderSlot,
+ onBeforeUnmount
} from '@vue/runtime-test'
import { PatchFlags, SlotFlags } from '@vue/shared'
@@ -449,4 +450,29 @@ describe('renderer: optimized mode', () => {
expect(inner(root)).toBe('
')
})
+
+ // #2169
+ // block
+ // - dynamic child (1)
+ // - component (2)
+ // When unmounting (1), we know we are in optimized mode so no need to further
+ // traverse unmount its children
+ test('should not perform unnecessary unmount traversals', () => {
+ const spy = jest.fn()
+ const Child = {
+ setup() {
+ onBeforeUnmount(spy)
+ return () => 'child'
+ }
+ }
+ const Parent = () => (
+ openBlock(),
+ createBlock('div', null, [
+ createVNode('div', { style: {} }, [createVNode(Child)], 4 /* STYLE */)
+ ])
+ )
+ render(h(Parent), root)
+ render(null, root)
+ expect(spy).toHaveBeenCalledTimes(1)
+ })
})
diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts
index 64d2d3c3..bb3129bd 100644
--- a/packages/runtime-core/src/renderer.ts
+++ b/packages/runtime-core/src/renderer.ts
@@ -213,7 +213,8 @@ type UnmountFn = (
vnode: VNode,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
- doRemove?: boolean
+ doRemove?: boolean,
+ optimized?: boolean
) => void
type RemoveFn = (vnode: VNode) => void
@@ -223,6 +224,7 @@ type UnmountChildrenFn = (
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
doRemove?: boolean,
+ optimized?: boolean,
start?: number
) => void
@@ -1647,7 +1649,14 @@ function baseCreateRenderer(
}
if (oldLength > newLength) {
// remove old
- unmountChildren(c1, parentComponent, parentSuspense, true, commonLength)
+ unmountChildren(
+ c1,
+ parentComponent,
+ parentSuspense,
+ true,
+ false,
+ commonLength
+ )
} else {
// mount new
mountChildren(
@@ -1968,7 +1977,8 @@ function baseCreateRenderer(
vnode,
parentComponent,
parentSuspense,
- doRemove = false
+ doRemove = false,
+ optimized = false
) => {
const {
type,
@@ -2016,8 +2026,14 @@ function baseCreateRenderer(
(patchFlag > 0 && patchFlag & PatchFlags.STABLE_FRAGMENT))
) {
// fast path for block nodes: only need to unmount dynamic children.
- unmountChildren(dynamicChildren, parentComponent, parentSuspense)
- } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
+ unmountChildren(
+ dynamicChildren,
+ parentComponent,
+ parentSuspense,
+ false,
+ true
+ )
+ } else if (!optimized && shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
unmountChildren(children as VNode[], parentComponent, parentSuspense)
}
@@ -2149,10 +2165,11 @@ function baseCreateRenderer(
parentComponent,
parentSuspense,
doRemove = false,
+ optimized = false,
start = 0
) => {
for (let i = start; i < children.length; i++) {
- unmount(children[i], parentComponent, parentSuspense, doRemove)
+ unmount(children[i], parentComponent, parentSuspense, doRemove, optimized)
}
}