fix: ensure vnode hooks are called consistently regardless of keep-alive
This commit is contained in:
parent
c9629f2692
commit
4e8e689572
@ -567,7 +567,7 @@ describe('KeepAlive', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not call onVnodeUnmounted', async () => {
|
it('should call correct vnode hooks', async () => {
|
||||||
const Foo = markRaw({
|
const Foo = markRaw({
|
||||||
name: 'Foo',
|
name: 'Foo',
|
||||||
render() {
|
render() {
|
||||||
@ -643,14 +643,16 @@ describe('KeepAlive', () => {
|
|||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
expect(spyMounted).toHaveBeenCalledTimes(2)
|
expect(spyMounted).toHaveBeenCalledTimes(2)
|
||||||
expect(spyUnmounted).toHaveBeenCalledTimes(0)
|
expect(spyUnmounted).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
toggle()
|
toggle()
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
expect(spyMounted).toHaveBeenCalledTimes(3)
|
||||||
|
expect(spyUnmounted).toHaveBeenCalledTimes(2)
|
||||||
|
|
||||||
render(null, root)
|
render(null, root)
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
expect(spyMounted).toHaveBeenCalledTimes(3)
|
||||||
expect(spyMounted).toHaveBeenCalledTimes(2)
|
expect(spyUnmounted).toHaveBeenCalledTimes(4)
|
||||||
expect(spyUnmounted).toHaveBeenCalledTimes(2)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -23,7 +23,8 @@ import {
|
|||||||
queuePostRenderEffect,
|
queuePostRenderEffect,
|
||||||
MoveType,
|
MoveType,
|
||||||
RendererElement,
|
RendererElement,
|
||||||
RendererNode
|
RendererNode,
|
||||||
|
invokeVNodeHook
|
||||||
} from '../renderer'
|
} from '../renderer'
|
||||||
import { setTransitionHooks } from './BaseTransition'
|
import { setTransitionHooks } from './BaseTransition'
|
||||||
import { ComponentRenderContext } from '../componentProxy'
|
import { ComponentRenderContext } from '../componentProxy'
|
||||||
@ -96,11 +97,11 @@ const KeepAliveImpl = {
|
|||||||
const storageContainer = createElement('div')
|
const storageContainer = createElement('div')
|
||||||
|
|
||||||
sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => {
|
sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => {
|
||||||
const child = vnode.component!
|
const instance = vnode.component!
|
||||||
move(vnode, container, anchor, MoveType.ENTER, parentSuspense)
|
move(vnode, container, anchor, MoveType.ENTER, parentSuspense)
|
||||||
// in case props have changed
|
// in case props have changed
|
||||||
patch(
|
patch(
|
||||||
child.vnode,
|
instance.vnode,
|
||||||
vnode,
|
vnode,
|
||||||
container,
|
container,
|
||||||
anchor,
|
anchor,
|
||||||
@ -110,27 +111,35 @@ const KeepAliveImpl = {
|
|||||||
optimized
|
optimized
|
||||||
)
|
)
|
||||||
queuePostRenderEffect(() => {
|
queuePostRenderEffect(() => {
|
||||||
child.isDeactivated = false
|
instance.isDeactivated = false
|
||||||
if (child.a) {
|
if (instance.a) {
|
||||||
invokeArrayFns(child.a)
|
invokeArrayFns(instance.a)
|
||||||
|
}
|
||||||
|
const vnodeHook = vnode.props && vnode.props.onVnodeMounted
|
||||||
|
if (vnodeHook) {
|
||||||
|
invokeVNodeHook(vnodeHook, instance.parent, vnode)
|
||||||
}
|
}
|
||||||
}, parentSuspense)
|
}, parentSuspense)
|
||||||
}
|
}
|
||||||
|
|
||||||
sharedContext.deactivate = (vnode: VNode) => {
|
sharedContext.deactivate = (vnode: VNode) => {
|
||||||
|
const instance = vnode.component!
|
||||||
move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense)
|
move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense)
|
||||||
queuePostRenderEffect(() => {
|
queuePostRenderEffect(() => {
|
||||||
const component = vnode.component!
|
if (instance.da) {
|
||||||
if (component.da) {
|
invokeArrayFns(instance.da)
|
||||||
invokeArrayFns(component.da)
|
|
||||||
}
|
}
|
||||||
component.isDeactivated = true
|
const vnodeHook = vnode.props && vnode.props.onVnodeUnmounted
|
||||||
|
if (vnodeHook) {
|
||||||
|
invokeVNodeHook(vnodeHook, instance.parent, vnode)
|
||||||
|
}
|
||||||
|
instance.isDeactivated = true
|
||||||
}, parentSuspense)
|
}, parentSuspense)
|
||||||
}
|
}
|
||||||
|
|
||||||
function unmount(vnode: VNode) {
|
function unmount(vnode: VNode) {
|
||||||
// reset the shapeFlag so it can be properly unmounted
|
// reset the shapeFlag so it can be properly unmounted
|
||||||
vnode.shapeFlag = ShapeFlags.STATEFUL_COMPONENT
|
resetShapeFlag(vnode)
|
||||||
_unmount(vnode, instance, parentSuspense)
|
_unmount(vnode, instance, parentSuspense)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +159,7 @@ const KeepAliveImpl = {
|
|||||||
} else if (current) {
|
} else if (current) {
|
||||||
// current active instance should no longer be kept-alive.
|
// current active instance should no longer be kept-alive.
|
||||||
// we can't unmount it now but it might be later, so reset its flag now.
|
// we can't unmount it now but it might be later, so reset its flag now.
|
||||||
current.shapeFlag = ShapeFlags.STATEFUL_COMPONENT
|
resetShapeFlag(current)
|
||||||
}
|
}
|
||||||
cache.delete(key)
|
cache.delete(key)
|
||||||
keys.delete(key)
|
keys.delete(key)
|
||||||
@ -165,7 +174,18 @@ const KeepAliveImpl = {
|
|||||||
)
|
)
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
cache.forEach(unmount)
|
cache.forEach(cached => {
|
||||||
|
const { subTree, suspense } = instance
|
||||||
|
if (cached.type === subTree.type) {
|
||||||
|
// current instance will be unmounted as part of keep-alive's unmount
|
||||||
|
resetShapeFlag(subTree)
|
||||||
|
// but invoke its deactivated hook here
|
||||||
|
const da = subTree.component!.da
|
||||||
|
da && queuePostRenderEffect(da, suspense)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
unmount(cached)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@ -197,7 +217,7 @@ const KeepAliveImpl = {
|
|||||||
(include && (!name || !matches(include, name))) ||
|
(include && (!name || !matches(include, name))) ||
|
||||||
(exclude && name && matches(exclude, name))
|
(exclude && name && matches(exclude, name))
|
||||||
) {
|
) {
|
||||||
return vnode
|
return (current = vnode)
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = vnode.key == null ? comp : vnode.key
|
const key = vnode.key == null ? comp : vnode.key
|
||||||
@ -325,3 +345,14 @@ function injectToKeepAliveRoot(
|
|||||||
remove(keepAliveRoot[type]!, hook)
|
remove(keepAliveRoot[type]!, hook)
|
||||||
}, target)
|
}, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetShapeFlag(vnode: VNode) {
|
||||||
|
let shapeFlag = vnode.shapeFlag
|
||||||
|
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
|
||||||
|
shapeFlag -= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
|
||||||
|
}
|
||||||
|
if (shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
|
||||||
|
shapeFlag -= ShapeFlags.COMPONENT_KEPT_ALIVE
|
||||||
|
}
|
||||||
|
vnode.shapeFlag = shapeFlag
|
||||||
|
}
|
||||||
|
@ -1866,25 +1866,25 @@ function baseCreateRenderer(
|
|||||||
patchFlag,
|
patchFlag,
|
||||||
dirs
|
dirs
|
||||||
} = vnode
|
} = vnode
|
||||||
const shouldInvokeDirs = shapeFlag & ShapeFlags.ELEMENT && dirs
|
|
||||||
const shouldKeepAlive = shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
|
|
||||||
let vnodeHook: VNodeHook | undefined | null
|
|
||||||
|
|
||||||
// unset ref
|
// unset ref
|
||||||
if (ref != null && parentComponent) {
|
if (ref != null && parentComponent) {
|
||||||
setRef(ref, null, parentComponent, null)
|
setRef(ref, null, parentComponent, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((vnodeHook = props && props.onVnodeBeforeUnmount) && !shouldKeepAlive) {
|
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
|
||||||
|
;(parentComponent!.ctx as KeepAliveContext).deactivate(vnode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldInvokeDirs = shapeFlag & ShapeFlags.ELEMENT && dirs
|
||||||
|
|
||||||
|
let vnodeHook: VNodeHook | undefined | null
|
||||||
|
if ((vnodeHook = props && props.onVnodeBeforeUnmount)) {
|
||||||
invokeVNodeHook(vnodeHook, parentComponent, vnode)
|
invokeVNodeHook(vnodeHook, parentComponent, vnode)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shapeFlag & ShapeFlags.COMPONENT) {
|
if (shapeFlag & ShapeFlags.COMPONENT) {
|
||||||
if (shouldKeepAlive) {
|
|
||||||
;(parentComponent!.ctx as KeepAliveContext).deactivate(vnode)
|
|
||||||
} else {
|
|
||||||
unmountComponent(vnode.component!, parentSuspense, doRemove)
|
unmountComponent(vnode.component!, parentSuspense, doRemove)
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
|
if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
|
||||||
vnode.suspense!.unmount(parentSuspense, doRemove)
|
vnode.suspense!.unmount(parentSuspense, doRemove)
|
||||||
@ -1917,10 +1917,7 @@ function baseCreateRenderer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if ((vnodeHook = props && props.onVnodeUnmounted) || shouldInvokeDirs) {
|
||||||
((vnodeHook = props && props.onVnodeUnmounted) || shouldInvokeDirs) &&
|
|
||||||
!shouldKeepAlive
|
|
||||||
) {
|
|
||||||
queuePostRenderEffect(() => {
|
queuePostRenderEffect(() => {
|
||||||
vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
|
vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
|
||||||
shouldInvokeDirs &&
|
shouldInvokeDirs &&
|
||||||
|
Loading…
x
Reference in New Issue
Block a user