Skip to content

Commit 4e8e689

Browse files
committed
fix: ensure vnode hooks are called consistently regardless of keep-alive
1 parent c9629f2 commit 4e8e689

File tree

3 files changed

+63
-33
lines changed

3 files changed

+63
-33
lines changed

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

+7-5
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ describe('KeepAlive', () => {
567567
})
568568
})
569569

570-
it('should not call onVnodeUnmounted', async () => {
570+
it('should call correct vnode hooks', async () => {
571571
const Foo = markRaw({
572572
name: 'Foo',
573573
render() {
@@ -643,14 +643,16 @@ describe('KeepAlive', () => {
643643
await nextTick()
644644

645645
expect(spyMounted).toHaveBeenCalledTimes(2)
646-
expect(spyUnmounted).toHaveBeenCalledTimes(0)
646+
expect(spyUnmounted).toHaveBeenCalledTimes(1)
647647

648648
toggle()
649649
await nextTick()
650+
expect(spyMounted).toHaveBeenCalledTimes(3)
651+
expect(spyUnmounted).toHaveBeenCalledTimes(2)
652+
650653
render(null, root)
651654
await nextTick()
652-
653-
expect(spyMounted).toHaveBeenCalledTimes(2)
654-
expect(spyUnmounted).toHaveBeenCalledTimes(2)
655+
expect(spyMounted).toHaveBeenCalledTimes(3)
656+
expect(spyUnmounted).toHaveBeenCalledTimes(4)
655657
})
656658
})

packages/runtime-core/src/components/KeepAlive.ts

+45-14
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ import {
2323
queuePostRenderEffect,
2424
MoveType,
2525
RendererElement,
26-
RendererNode
26+
RendererNode,
27+
invokeVNodeHook
2728
} from '../renderer'
2829
import { setTransitionHooks } from './BaseTransition'
2930
import { ComponentRenderContext } from '../componentProxy'
@@ -96,11 +97,11 @@ const KeepAliveImpl = {
9697
const storageContainer = createElement('div')
9798

9899
sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => {
99-
const child = vnode.component!
100+
const instance = vnode.component!
100101
move(vnode, container, anchor, MoveType.ENTER, parentSuspense)
101102
// in case props have changed
102103
patch(
103-
child.vnode,
104+
instance.vnode,
104105
vnode,
105106
container,
106107
anchor,
@@ -110,27 +111,35 @@ const KeepAliveImpl = {
110111
optimized
111112
)
112113
queuePostRenderEffect(() => {
113-
child.isDeactivated = false
114-
if (child.a) {
115-
invokeArrayFns(child.a)
114+
instance.isDeactivated = false
115+
if (instance.a) {
116+
invokeArrayFns(instance.a)
117+
}
118+
const vnodeHook = vnode.props && vnode.props.onVnodeMounted
119+
if (vnodeHook) {
120+
invokeVNodeHook(vnodeHook, instance.parent, vnode)
116121
}
117122
}, parentSuspense)
118123
}
119124

120125
sharedContext.deactivate = (vnode: VNode) => {
126+
const instance = vnode.component!
121127
move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense)
122128
queuePostRenderEffect(() => {
123-
const component = vnode.component!
124-
if (component.da) {
125-
invokeArrayFns(component.da)
129+
if (instance.da) {
130+
invokeArrayFns(instance.da)
131+
}
132+
const vnodeHook = vnode.props && vnode.props.onVnodeUnmounted
133+
if (vnodeHook) {
134+
invokeVNodeHook(vnodeHook, instance.parent, vnode)
126135
}
127-
component.isDeactivated = true
136+
instance.isDeactivated = true
128137
}, parentSuspense)
129138
}
130139

131140
function unmount(vnode: VNode) {
132141
// reset the shapeFlag so it can be properly unmounted
133-
vnode.shapeFlag = ShapeFlags.STATEFUL_COMPONENT
142+
resetShapeFlag(vnode)
134143
_unmount(vnode, instance, parentSuspense)
135144
}
136145

@@ -150,7 +159,7 @@ const KeepAliveImpl = {
150159
} else if (current) {
151160
// current active instance should no longer be kept-alive.
152161
// we can't unmount it now but it might be later, so reset its flag now.
153-
current.shapeFlag = ShapeFlags.STATEFUL_COMPONENT
162+
resetShapeFlag(current)
154163
}
155164
cache.delete(key)
156165
keys.delete(key)
@@ -165,7 +174,18 @@ const KeepAliveImpl = {
165174
)
166175

167176
onBeforeUnmount(() => {
168-
cache.forEach(unmount)
177+
cache.forEach(cached => {
178+
const { subTree, suspense } = instance
179+
if (cached.type === subTree.type) {
180+
// current instance will be unmounted as part of keep-alive's unmount
181+
resetShapeFlag(subTree)
182+
// but invoke its deactivated hook here
183+
const da = subTree.component!.da
184+
da && queuePostRenderEffect(da, suspense)
185+
return
186+
}
187+
unmount(cached)
188+
})
169189
})
170190

171191
return () => {
@@ -197,7 +217,7 @@ const KeepAliveImpl = {
197217
(include && (!name || !matches(include, name))) ||
198218
(exclude && name && matches(exclude, name))
199219
) {
200-
return vnode
220+
return (current = vnode)
201221
}
202222

203223
const key = vnode.key == null ? comp : vnode.key
@@ -325,3 +345,14 @@ function injectToKeepAliveRoot(
325345
remove(keepAliveRoot[type]!, hook)
326346
}, target)
327347
}
348+
349+
function resetShapeFlag(vnode: VNode) {
350+
let shapeFlag = vnode.shapeFlag
351+
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
352+
shapeFlag -= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
353+
}
354+
if (shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
355+
shapeFlag -= ShapeFlags.COMPONENT_KEPT_ALIVE
356+
}
357+
vnode.shapeFlag = shapeFlag
358+
}

packages/runtime-core/src/renderer.ts

+11-14
Original file line numberDiff line numberDiff line change
@@ -1866,25 +1866,25 @@ function baseCreateRenderer(
18661866
patchFlag,
18671867
dirs
18681868
} = vnode
1869-
const shouldInvokeDirs = shapeFlag & ShapeFlags.ELEMENT && dirs
1870-
const shouldKeepAlive = shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
1871-
let vnodeHook: VNodeHook | undefined | null
1872-
18731869
// unset ref
18741870
if (ref != null && parentComponent) {
18751871
setRef(ref, null, parentComponent, null)
18761872
}
18771873

1878-
if ((vnodeHook = props && props.onVnodeBeforeUnmount) && !shouldKeepAlive) {
1874+
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
1875+
;(parentComponent!.ctx as KeepAliveContext).deactivate(vnode)
1876+
return
1877+
}
1878+
1879+
const shouldInvokeDirs = shapeFlag & ShapeFlags.ELEMENT && dirs
1880+
1881+
let vnodeHook: VNodeHook | undefined | null
1882+
if ((vnodeHook = props && props.onVnodeBeforeUnmount)) {
18791883
invokeVNodeHook(vnodeHook, parentComponent, vnode)
18801884
}
18811885

18821886
if (shapeFlag & ShapeFlags.COMPONENT) {
1883-
if (shouldKeepAlive) {
1884-
;(parentComponent!.ctx as KeepAliveContext).deactivate(vnode)
1885-
} else {
1886-
unmountComponent(vnode.component!, parentSuspense, doRemove)
1887-
}
1887+
unmountComponent(vnode.component!, parentSuspense, doRemove)
18881888
} else {
18891889
if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
18901890
vnode.suspense!.unmount(parentSuspense, doRemove)
@@ -1917,10 +1917,7 @@ function baseCreateRenderer(
19171917
}
19181918
}
19191919

1920-
if (
1921-
((vnodeHook = props && props.onVnodeUnmounted) || shouldInvokeDirs) &&
1922-
!shouldKeepAlive
1923-
) {
1920+
if ((vnodeHook = props && props.onVnodeUnmounted) || shouldInvokeDirs) {
19241921
queuePostRenderEffect(() => {
19251922
vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
19261923
shouldInvokeDirs &&

0 commit comments

Comments
 (0)