diff --git a/packages/runtime-core/__tests__/hmr.spec.ts b/packages/runtime-core/__tests__/hmr.spec.ts index eaef8d401a7..4b501052ce4 100644 --- a/packages/runtime-core/__tests__/hmr.spec.ts +++ b/packages/runtime-core/__tests__/hmr.spec.ts @@ -151,6 +151,73 @@ describe('hot module replacement', () => { expect(mountSpy).toHaveBeenCalledTimes(1) }) + // #7042 + test('reload KeepAlive slot', async () => { + const root = nodeOps.createElement('div') + const childId = 'test-child-keep-alive' + const unmountSpy = jest.fn() + const mountSpy = jest.fn() + const activeSpy = jest.fn() + const deactiveSpy = jest.fn() + + const Child: ComponentOptions = { + __hmrId: childId, + data() { + return { count: 0 } + }, + unmounted: unmountSpy, + render: compileToFunction(`
{{ count }}
`) + } + createRecord(childId, Child) + + const Parent: ComponentOptions = { + components: { Child }, + data() { + return { toggle: true } + }, + render: compileToFunction( + `` + ) + } + + render(h(Parent), root) + expect(serializeInner(root)).toBe(`
0
`) + + reload(childId, { + __hmrId: childId, + data() { + return { count: 1 } + }, + mounted: mountSpy, + unmounted: unmountSpy, + activated: activeSpy, + deactivated: deactiveSpy, + render: compileToFunction(`
{{ count }}
`) + }) + await nextTick() + expect(serializeInner(root)).toBe(`
1
`) + expect(unmountSpy).toHaveBeenCalledTimes(1) + expect(mountSpy).toHaveBeenCalledTimes(1) + expect(activeSpy).toHaveBeenCalledTimes(1) + expect(deactiveSpy).toHaveBeenCalledTimes(0) + + // should not unmount when toggling + triggerEvent(root.children[1] as TestElement, 'click') + await nextTick() + expect(unmountSpy).toHaveBeenCalledTimes(1) + expect(mountSpy).toHaveBeenCalledTimes(1) + expect(activeSpy).toHaveBeenCalledTimes(1) + expect(deactiveSpy).toHaveBeenCalledTimes(1) + + // should not mount when toggling + triggerEvent(root.children[1] as TestElement, 'click') + await nextTick() + expect(unmountSpy).toHaveBeenCalledTimes(1) + expect(mountSpy).toHaveBeenCalledTimes(1) + expect(activeSpy).toHaveBeenCalledTimes(2) + expect(deactiveSpy).toHaveBeenCalledTimes(1) + }) + test('reload class component', async () => { const root = nodeOps.createElement('div') const childId = 'test4-child' diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index 9605d79150c..3fec48140fc 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -31,7 +31,6 @@ import { invokeArrayFns } from '@vue/shared' import { watch } from '../apiWatch' -import { hmrDirtyComponents } from '../hmr' import { RendererInternals, queuePostRenderEffect, @@ -281,8 +280,7 @@ const KeepAliveImpl: ComponentOptions = { if ( (include && (!name || !matches(include, name))) || - (exclude && name && matches(exclude, name)) || - (__DEV__ && hmrDirtyComponents.has(comp)) + (exclude && name && matches(exclude, name)) ) { current = vnode return rawVNode diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index 7d8017e650a..41f848e44de 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -357,6 +357,14 @@ export function isSameVNodeType(n1: VNode, n2: VNode): boolean { n2.shapeFlag & ShapeFlags.COMPONENT && hmrDirtyComponents.has(n2.type as ConcreteComponent) ) { + // #7042, ensure the vnode being unmounted during HMR + if (n1.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) { + n1.shapeFlag -= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE + } + // #7042, ensure the vnode being mounted as fresh during HMR + if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) { + n2.shapeFlag -= ShapeFlags.COMPONENT_KEPT_ALIVE + } // HMR only: if the component has been hot-updated, force a reload. return false }