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) {
 | 
					      unmountComponent(vnode.component!, parentSuspense, doRemove)
 | 
				
			||||||
        ;(parentComponent!.ctx as KeepAliveContext).deactivate(vnode)
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        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