diff --git a/packages/runtime-core/src/compat/globalConfig.ts b/packages/runtime-core/src/compat/globalConfig.ts index bf7b9189e07..593cd20e31b 100644 --- a/packages/runtime-core/src/compat/globalConfig.ts +++ b/packages/runtime-core/src/compat/globalConfig.ts @@ -113,11 +113,15 @@ export const legacyOptionMergeStrats = { watch: mergeObjectOptions } +function toArray(target: any) { + return isArray(target) ? target : target ? [target] : [] +} + function mergeHook( to: Function[] | Function | undefined, from: Function | Function[] ) { - return Array.from(new Set([...(isArray(to) ? to : to ? [to] : []), from])) + return Array.from(new Set([...toArray(to), ...toArray(from)])) } function mergeObjectOptions(to: Object | undefined, from: Object | undefined) { diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts index 40f5669b508..0fd25bc7156 100644 --- a/packages/runtime-core/src/componentOptions.ts +++ b/packages/runtime-core/src/componentOptions.ts @@ -767,55 +767,44 @@ export function applyOptions( globalMixins ) } - if (beforeMount) { - onBeforeMount(beforeMount.bind(publicThis)) - } - if (mounted) { - onMounted(mounted.bind(publicThis)) - } - if (beforeUpdate) { - onBeforeUpdate(beforeUpdate.bind(publicThis)) - } - if (updated) { - onUpdated(updated.bind(publicThis)) - } - if (activated) { - onActivated(activated.bind(publicThis)) - } - if (deactivated) { - onDeactivated(deactivated.bind(publicThis)) - } - if (errorCaptured) { - onErrorCaptured(errorCaptured.bind(publicThis)) - } - if (renderTracked) { - onRenderTracked(renderTracked.bind(publicThis)) - } - if (renderTriggered) { - onRenderTriggered(renderTriggered.bind(publicThis)) - } - if (beforeUnmount) { - onBeforeUnmount(beforeUnmount.bind(publicThis)) - } - if (unmounted) { - onUnmounted(unmounted.bind(publicThis)) - } - if (serverPrefetch) { - onServerPrefetch(serverPrefetch.bind(publicThis)) + + function registerLifecycleHook( + register: Function, + hook?: Function | Function[] + ) { + // Array lifecycle hooks are only present in the compat build + if (__COMPAT__ && isArray(hook)) { + hook.forEach(_hook => register(_hook.bind(publicThis))) + } else if (hook) { + register((hook as Function).bind(publicThis)) + } } + registerLifecycleHook(onBeforeMount, beforeMount) + registerLifecycleHook(onMounted, mounted) + registerLifecycleHook(onBeforeUpdate, beforeUpdate) + registerLifecycleHook(onUpdated, updated) + registerLifecycleHook(onActivated, activated) + registerLifecycleHook(onDeactivated, deactivated) + registerLifecycleHook(onErrorCaptured, errorCaptured) + registerLifecycleHook(onRenderTracked, renderTracked) + registerLifecycleHook(onRenderTriggered, renderTriggered) + registerLifecycleHook(onBeforeUnmount, beforeUnmount) + registerLifecycleHook(onUnmounted, unmounted) + registerLifecycleHook(onServerPrefetch, serverPrefetch) + if (__COMPAT__) { if ( beforeDestroy && softAssertCompatEnabled(DeprecationTypes.OPTIONS_BEFORE_DESTROY, instance) ) { - onBeforeUnmount(beforeDestroy.bind(publicThis)) + registerLifecycleHook(onBeforeUnmount, beforeDestroy) } if ( destroyed && softAssertCompatEnabled(DeprecationTypes.OPTIONS_DESTROYED, instance) ) { - onUnmounted(destroyed.bind(publicThis)) + registerLifecycleHook(onUnmounted, destroyed) } } diff --git a/packages/vue-compat/__tests__/global.spec.ts b/packages/vue-compat/__tests__/global.spec.ts index 02d578772b7..6ef28f1ddbf 100644 --- a/packages/vue-compat/__tests__/global.spec.ts +++ b/packages/vue-compat/__tests__/global.spec.ts @@ -145,22 +145,31 @@ describe('GLOBAL_EXTEND', () => { }) it('should not merge nested mixins created with Vue.extend', () => { + const a = jest.fn(); + const b = jest.fn(); + const c = jest.fn(); + const d = jest.fn(); const A = Vue.extend({ - created: () => {} + created: a }) const B = Vue.extend({ mixins: [A], - created: () => {} + created: b }) const C = Vue.extend({ extends: B, - created: () => {} + created: c }) const D = Vue.extend({ mixins: [C], - created: () => {} + created: d, + render() { return null }, }) - expect(D.options.created!.length).toBe(4) + new D().$mount() + expect(a.mock.calls.length).toStrictEqual(1) + expect(b.mock.calls.length).toStrictEqual(1) + expect(c.mock.calls.length).toStrictEqual(1) + expect(d.mock.calls.length).toStrictEqual(1) }) it('should merge methods', () => { diff --git a/packages/vue-compat/__tests__/options.spec.ts b/packages/vue-compat/__tests__/options.spec.ts index ca8ea807381..d0225dc964b 100644 --- a/packages/vue-compat/__tests__/options.spec.ts +++ b/packages/vue-compat/__tests__/options.spec.ts @@ -10,7 +10,8 @@ beforeEach(() => { toggleDeprecationWarning(true) Vue.configureCompat({ MODE: 2, - GLOBAL_MOUNT: 'suppress-warning' + GLOBAL_MOUNT: 'suppress-warning', + GLOBAL_EXTEND: 'suppress-warning' }) }) @@ -90,3 +91,35 @@ test('beforeDestroy/destroyed', async () => { deprecationData[DeprecationTypes.OPTIONS_DESTROYED].message ).toHaveBeenWarned() }) + +test('beforeDestroy/destroyed in Vue.extend components', async () => { + const beforeDestroy = jest.fn() + const destroyed = jest.fn() + + const child = Vue.extend({ + template: `foo`, + beforeDestroy, + destroyed + }) + + const vm = new Vue({ + template: ``, + data() { + return { ok: true } + }, + components: { child } + }).$mount() as any + + vm.ok = false + await nextTick() + expect(beforeDestroy).toHaveBeenCalled() + expect(destroyed).toHaveBeenCalled() + + expect( + deprecationData[DeprecationTypes.OPTIONS_BEFORE_DESTROY].message + ).toHaveBeenWarned() + + expect( + deprecationData[DeprecationTypes.OPTIONS_DESTROYED].message + ).toHaveBeenWarned() +})