diff --git a/packages-private/dts-test/defineComponent.test-d.tsx b/packages-private/dts-test/defineComponent.test-d.tsx index 0124a8b1525..6b9144bf297 100644 --- a/packages-private/dts-test/defineComponent.test-d.tsx +++ b/packages-private/dts-test/defineComponent.test-d.tsx @@ -1041,6 +1041,18 @@ describe('emits', () => { }, }) + // #11803 manual props annotation in setup() + const Hello = defineComponent({ + name: 'HelloWorld', + inheritAttrs: false, + props: { foo: String }, + emits: { + customClick: (args: string) => typeof args === 'string', + }, + setup(props: { foo?: string }) {}, + }) + ; {}} /> + // without emits defineComponent({ setup(props, { emit }) { diff --git a/packages/runtime-core/src/apiDefineComponent.ts b/packages/runtime-core/src/apiDefineComponent.ts index 0a58954526c..5a5f56fe6f7 100644 --- a/packages/runtime-core/src/apiDefineComponent.ts +++ b/packages/runtime-core/src/apiDefineComponent.ts @@ -134,6 +134,9 @@ export type DefineSetupFnComponent< S > +type ToResolvedProps = Readonly & + Readonly> + // defineComponent is a utility that is primarily used for type inference // when declaring components. Type inference is provided in the component // options (provided as the argument). The returned value has artificial types @@ -210,8 +213,6 @@ export function defineComponent< : ExtractPropTypes : { [key in RuntimePropsKeys]?: any } : TypeProps, - ResolvedProps = Readonly & - Readonly>, TypeRefs extends Record = {}, >( options: { @@ -229,7 +230,7 @@ export function defineComponent< */ __typeRefs?: TypeRefs } & ComponentOptionsBase< - ResolvedProps, + ToResolvedProps, SetupBindings, Data, Computed, @@ -249,7 +250,7 @@ export function defineComponent< > & ThisType< CreateComponentPublicInstanceWithMixins< - ResolvedProps, + ToResolvedProps, SetupBindings, Data, Computed, @@ -278,7 +279,7 @@ export function defineComponent< ResolvedEmits, RuntimeEmitsKeys, PublicProps, - ResolvedProps, + ToResolvedProps, ExtractDefaultPropTypes, Slots, LocalComponents, diff --git a/packages/runtime-core/src/componentRenderUtils.ts b/packages/runtime-core/src/componentRenderUtils.ts index 5badb04b006..9fe381ff645 100644 --- a/packages/runtime-core/src/componentRenderUtils.ts +++ b/packages/runtime-core/src/componentRenderUtils.ts @@ -60,6 +60,7 @@ export function renderComponentRoot( setupState, ctx, inheritAttrs, + isMounted, } = instance const prev = setCurrentRenderingInstance(instance) @@ -253,7 +254,9 @@ export function renderComponentRoot( `that cannot be animated.`, ) } - root.transition = vnode.transition + root.transition = isMounted + ? vnode.component!.subTree.transition! + : vnode.transition } if (__DEV__ && setRoot) { diff --git a/packages/runtime-core/src/components/BaseTransition.ts b/packages/runtime-core/src/components/BaseTransition.ts index 37534ad699f..568a6382bfe 100644 --- a/packages/runtime-core/src/components/BaseTransition.ts +++ b/packages/runtime-core/src/components/BaseTransition.ts @@ -227,6 +227,7 @@ const BaseTransitionImpl: ComponentOptions = { if (!(instance.job.flags! & SchedulerJobFlags.DISPOSED)) { instance.update() } + delete leavingHooks.afterLeave } return emptyPlaceholder(child) } else if (mode === 'in-out' && innerChild.type !== Comment) { diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index a87f44cc8fa..dd1d1f5a6e3 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -267,7 +267,7 @@ const KeepAliveImpl: ComponentOptions = { pendingCacheKey = null if (!slots.default) { - return null + return (current = null) } const children = slots.default() diff --git a/packages/vue/__tests__/e2e/Transition.spec.ts b/packages/vue/__tests__/e2e/Transition.spec.ts index b9e9117289e..8cdda4dc63e 100644 --- a/packages/vue/__tests__/e2e/Transition.spec.ts +++ b/packages/vue/__tests__/e2e/Transition.spec.ts @@ -1427,9 +1427,11 @@ describe('e2e: Transition', () => { }, E2E_TIMEOUT, ) + }) + describe('transition with KeepAlive', () => { test( - 'w/ KeepAlive + unmount innerChild', + 'unmount innerChild (out-in mode)', async () => { const unmountSpy = vi.fn() await page().exposeFunction('unmountSpy', unmountSpy) @@ -1484,6 +1486,173 @@ describe('e2e: Transition', () => { }, E2E_TIMEOUT, ) + + // #11775 + test( + 'switch child then update include (out-in mode)', + async () => { + const onUpdatedSpyA = vi.fn() + const onUnmountedSpyC = vi.fn() + + await page().exposeFunction('onUpdatedSpyA', onUpdatedSpyA) + await page().exposeFunction('onUnmountedSpyC', onUnmountedSpyC) + + await page().evaluate(() => { + const { onUpdatedSpyA, onUnmountedSpyC } = window as any + const { createApp, ref, shallowRef, h, onUpdated, onUnmounted } = ( + window as any + ).Vue + createApp({ + template: ` +
+ + + + + +
+ + + + `, + components: { + CompA: { + name: 'CompA', + setup() { + onUpdated(onUpdatedSpyA) + return () => h('div', 'CompA') + }, + }, + CompB: { + name: 'CompB', + setup() { + return () => h('div', 'CompB') + }, + }, + CompC: { + name: 'CompC', + setup() { + onUnmounted(onUnmountedSpyC) + return () => h('div', 'CompC') + }, + }, + }, + setup: () => { + const includeRef = ref(['CompA', 'CompB', 'CompC']) + const current = shallowRef('CompA') + const switchToB = () => (current.value = 'CompB') + const switchToC = () => (current.value = 'CompC') + const switchToA = () => { + current.value = 'CompA' + includeRef.value = ['CompA'] + } + return { current, switchToB, switchToC, switchToA, includeRef } + }, + }).mount('#app') + }) + + await transitionFinish() + expect(await html('#container')).toBe('
CompA
') + + await click('#switchToB') + await nextTick() + await click('#switchToC') + await transitionFinish() + expect(await html('#container')).toBe('
CompC
') + + await click('#switchToA') + await transitionFinish() + expect(await html('#container')).toBe('
CompA
') + + // expect CompA only update once + expect(onUpdatedSpyA).toBeCalledTimes(1) + expect(onUnmountedSpyC).toBeCalledTimes(1) + }, + E2E_TIMEOUT, + ) + + // #10827 + test( + 'switch and update child then update include (out-in mode)', + async () => { + const onUnmountedSpyB = vi.fn() + await page().exposeFunction('onUnmountedSpyB', onUnmountedSpyB) + + await page().evaluate(() => { + const { onUnmountedSpyB } = window as any + const { + createApp, + ref, + shallowRef, + h, + provide, + inject, + onUnmounted, + } = (window as any).Vue + createApp({ + template: ` +
+ + + + + +
+ + + `, + components: { + CompA: { + name: 'CompA', + setup() { + const current = inject('current') + return () => h('div', current.value) + }, + }, + CompB: { + name: 'CompB', + setup() { + const current = inject('current') + onUnmounted(onUnmountedSpyB) + return () => h('div', current.value) + }, + }, + }, + setup: () => { + const includeRef = ref(['CompA']) + const current = shallowRef('CompA') + provide('current', current) + + const switchToB = () => { + current.value = 'CompB' + includeRef.value = ['CompA', 'CompB'] + } + const switchToA = () => { + current.value = 'CompA' + includeRef.value = ['CompA'] + } + return { current, switchToB, switchToA, includeRef } + }, + }).mount('#app') + }) + + await transitionFinish() + expect(await html('#container')).toBe('
CompA
') + + await click('#switchToB') + await transitionFinish() + await transitionFinish() + expect(await html('#container')).toBe('
CompB
') + + await click('#switchToA') + await transitionFinish() + await transitionFinish() + expect(await html('#container')).toBe('
CompA
') + + expect(onUnmountedSpyB).toBeCalledTimes(1) + }, + E2E_TIMEOUT, + ) }) describe('transition with Suspense', () => {