mirror of https://github.com/vuejs/core.git
Merge 436296c5f1
into 56be3dd4db
This commit is contained in:
commit
8e68075976
|
@ -1173,4 +1173,48 @@ describe('KeepAlive', () => {
|
|||
expect(deactivatedHome).toHaveBeenCalledTimes(0)
|
||||
expect(unmountedHome).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
// #12017
|
||||
test('avoid duplicate mounts of deactivate components', async () => {
|
||||
const About = {
|
||||
name: 'About',
|
||||
setup() {
|
||||
return () => h('h1', 'About')
|
||||
},
|
||||
}
|
||||
const mountedHome = vi.fn()
|
||||
const Home = {
|
||||
name: 'Home',
|
||||
setup() {
|
||||
onMounted(mountedHome)
|
||||
return () => h('h1', 'Home')
|
||||
},
|
||||
}
|
||||
const activeView = shallowRef(About)
|
||||
const HomeView = {
|
||||
name: 'HomeView',
|
||||
setup() {
|
||||
return () => h(activeView.value)
|
||||
},
|
||||
}
|
||||
|
||||
const App = createApp({
|
||||
setup() {
|
||||
return () => {
|
||||
return [
|
||||
h(KeepAlive, null, [
|
||||
h(HomeView, {
|
||||
key: activeView.value.name,
|
||||
}),
|
||||
]),
|
||||
]
|
||||
}
|
||||
},
|
||||
})
|
||||
App.mount(nodeOps.createElement('div'))
|
||||
expect(mountedHome).toHaveBeenCalledTimes(0)
|
||||
activeView.value = Home
|
||||
await nextTick()
|
||||
expect(mountedHome).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -506,9 +506,16 @@ export interface ComponentInternalInstance {
|
|||
*/
|
||||
asyncResolved: boolean
|
||||
|
||||
/**
|
||||
* effects to be triggered on component activated in keep-alive
|
||||
* @internal
|
||||
*/
|
||||
activatedEffects?: Function[]
|
||||
|
||||
// lifecycle
|
||||
isMounted: boolean
|
||||
isUnmounted: boolean
|
||||
isActivated: boolean
|
||||
isDeactivated: boolean
|
||||
/**
|
||||
* @internal
|
||||
|
@ -673,6 +680,7 @@ export function createComponentInstance(
|
|||
// not using enums here because it results in computed properties
|
||||
isMounted: false,
|
||||
isUnmounted: false,
|
||||
isActivated: true,
|
||||
isDeactivated: false,
|
||||
bc: null,
|
||||
c: null,
|
||||
|
|
|
@ -48,6 +48,7 @@ import { devtoolsComponentAdded } from '../devtools'
|
|||
import { isAsyncWrapper } from '../apiAsyncComponent'
|
||||
import { isSuspense } from './Suspense'
|
||||
import { LifecycleHooks } from '../enums'
|
||||
import { queuePostFlushCb } from '../scheduler'
|
||||
|
||||
type MatchPattern = string | RegExp | (string | RegExp)[]
|
||||
|
||||
|
@ -136,6 +137,7 @@ const KeepAliveImpl: ComponentOptions = {
|
|||
optimized,
|
||||
) => {
|
||||
const instance = vnode.component!
|
||||
instance.isActivated = true
|
||||
move(vnode, container, anchor, MoveType.ENTER, parentSuspense)
|
||||
// in case props have changed
|
||||
patch(
|
||||
|
@ -149,6 +151,13 @@ const KeepAliveImpl: ComponentOptions = {
|
|||
vnode.slotScopeIds,
|
||||
optimized,
|
||||
)
|
||||
|
||||
const effects = instance.activatedEffects
|
||||
if (effects) {
|
||||
queuePostFlushCb(effects)
|
||||
instance.activatedEffects!.length = 0
|
||||
}
|
||||
|
||||
queuePostRenderEffect(() => {
|
||||
instance.isDeactivated = false
|
||||
if (instance.a) {
|
||||
|
@ -168,6 +177,7 @@ const KeepAliveImpl: ComponentOptions = {
|
|||
|
||||
sharedContext.deactivate = (vnode: VNode) => {
|
||||
const instance = vnode.component!
|
||||
instance.isActivated = false
|
||||
invalidateMount(instance.m)
|
||||
invalidateMount(instance.a)
|
||||
|
||||
|
|
|
@ -1431,6 +1431,21 @@ function baseCreateRenderer(
|
|||
} else {
|
||||
let { next, bu, u, parent, vnode } = instance
|
||||
|
||||
// skip updates while parent component is deactivated
|
||||
// but store effects for next activation
|
||||
const deactivatedParent = locateDeactivatedParent(instance)
|
||||
if (deactivatedParent) {
|
||||
;(
|
||||
deactivatedParent.activatedEffects ||
|
||||
(deactivatedParent.activatedEffects = [])
|
||||
).push(() => {
|
||||
if (!instance.isUnmounted) {
|
||||
update()
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (__FEATURE_SUSPENSE__) {
|
||||
const nonHydratedAsyncRoot = locateNonHydratedAsyncRoot(instance)
|
||||
// we are trying to update some async comp before hydration
|
||||
|
@ -2571,6 +2586,19 @@ function locateNonHydratedAsyncRoot(
|
|||
}
|
||||
}
|
||||
|
||||
function locateDeactivatedParent(instance: ComponentInternalInstance | null) {
|
||||
while (instance) {
|
||||
if (!instance.isActivated) {
|
||||
return instance
|
||||
}
|
||||
if (isKeepAlive(instance.vnode)) {
|
||||
break
|
||||
}
|
||||
instance = instance.parent
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function invalidateMount(hooks: LifecycleHook): void {
|
||||
if (hooks) {
|
||||
for (let i = 0; i < hooks.length; i++)
|
||||
|
|
Loading…
Reference in New Issue