diff --git a/packages/runtime-core/__tests__/components/BaseTransition.spec.ts b/packages/runtime-core/__tests__/components/BaseTransition.spec.ts index aaeae3fb4..ad5e534a5 100644 --- a/packages/runtime-core/__tests__/components/BaseTransition.spec.ts +++ b/packages/runtime-core/__tests__/components/BaseTransition.spec.ts @@ -1198,4 +1198,54 @@ describe('BaseTransition', () => { test('should not error on KeepAlive w/ function children', () => { expect(() => mount({}, () => () => h('div'), true)).not.toThrow() }) + + // #12091 + test('ensure the correct order of hook execution during the mounted phase toggle', async () => { + const toggle = ref(false) + const visible = ref(true) + const hooks: string[] = [] + + const Home = { + setup() { + return () => + h( + BaseTransition, + { + appear: true, + onBeforeEnter: () => hooks.push('beforeEnter'), + onEnter: (el, done) => hooks.push('enter'), + onEnterCancelled: () => hooks.push('enterCancelled'), + onAfterEnter: () => hooks.push('afterEnter'), + onBeforeLeave: () => hooks.push('beforeLeave'), + onLeave: () => hooks.push('leave'), + }, + () => (visible.value ? h('div') : null), + ) + }, + } + const About = { + setup() { + visible.value = false + return () => null + }, + } + + const root = nodeOps.createElement('div') + const App = { + setup() { + return () => (toggle.value ? [h(Home), h(About)] : null) + }, + } + render(h(App), root) + + setTimeout(async () => { + toggle.value = true + + await nextTick() + + expect(hooks.join('-')).eq( + `beforeEnter-enter-enterCancelled-beforeLeave-leave`, + ) + }) + }) }) diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts index a94ff3568..b73cb491a 100644 --- a/packages/runtime-core/src/hydration.ts +++ b/packages/runtime-core/src/hydration.ts @@ -532,14 +532,12 @@ export function createHydrationFunctions( if (dirs) { invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount') } - if ( - (vnodeHooks = props && props.onVnodeMounted) || - dirs || - needCallTransitionHooks - ) { + if (needCallTransitionHooks) { + transition!.enter(el) + } + if ((vnodeHooks = props && props.onVnodeMounted) || dirs) { queueEffectWithSuspense(() => { vnodeHooks && invokeVNodeHook(vnodeHooks, parentComponent, vnode) - needCallTransitionHooks && transition!.enter(el) dirs && invokeDirectiveHook(vnode, null, parentComponent, 'mounted') }, parentSuspense) } diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 022571050..97f681cb0 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -712,14 +712,12 @@ function baseCreateRenderer( transition!.beforeEnter(el) } hostInsert(el, container, anchor) - if ( - (vnodeHook = props && props.onVnodeMounted) || - needCallTransitionHooks || - dirs - ) { + if (needCallTransitionHooks) { + transition!.enter(el) + } + if ((vnodeHook = props && props.onVnodeMounted) || dirs) { queuePostRenderEffect(() => { vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode) - needCallTransitionHooks && transition!.enter(el) dirs && invokeDirectiveHook(vnode, null, parentComponent, 'mounted') }, parentSuspense) }