Skip to content

Commit

Permalink
fix: correctly merge lifecycle hooks when using Vue.extend
Browse files Browse the repository at this point in the history
  • Loading branch information
CyberAP committed May 11, 2021
1 parent 0e3bbd0 commit e52193d
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 22 deletions.
4 changes: 2 additions & 2 deletions packages/runtime-core/src/compat/globalConfig.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { extend, isArray } from '@vue/shared'
import { extend, toArray } from '@vue/shared'
import { AppConfig } from '../apiCreateApp'
import { mergeDataOption } from './data'
import {
Expand Down Expand Up @@ -117,7 +117,7 @@ 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) {
Expand Down
34 changes: 20 additions & 14 deletions packages/runtime-core/src/componentOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -768,54 +768,54 @@ export function applyOptions(
)
}
if (beforeMount) {
onBeforeMount(beforeMount.bind(publicThis))
runLifecycleHook(onBeforeMount, beforeMount, publicThis)
}
if (mounted) {
onMounted(mounted.bind(publicThis))
runLifecycleHook(onMounted, mounted, publicThis)
}
if (beforeUpdate) {
onBeforeUpdate(beforeUpdate.bind(publicThis))
runLifecycleHook(onBeforeUpdate, beforeUpdate, publicThis)
}
if (updated) {
onUpdated(updated.bind(publicThis))
runLifecycleHook(onUpdated, updated, publicThis)
}
if (activated) {
onActivated(activated.bind(publicThis))
runLifecycleHook(onActivated, activated, publicThis)
}
if (deactivated) {
onDeactivated(deactivated.bind(publicThis))
runLifecycleHook(onDeactivated, deactivated, publicThis)
}
if (errorCaptured) {
onErrorCaptured(errorCaptured.bind(publicThis))
runLifecycleHook(onErrorCaptured, errorCaptured, publicThis)
}
if (renderTracked) {
onRenderTracked(renderTracked.bind(publicThis))
runLifecycleHook(onRenderTracked, renderTracked, publicThis)
}
if (renderTriggered) {
onRenderTriggered(renderTriggered.bind(publicThis))
runLifecycleHook(onRenderTriggered, renderTriggered, publicThis)
}
if (beforeUnmount) {
onBeforeUnmount(beforeUnmount.bind(publicThis))
runLifecycleHook(onBeforeUnmount, beforeUnmount, publicThis)
}
if (unmounted) {
onUnmounted(unmounted.bind(publicThis))
runLifecycleHook(onUnmounted, unmounted, publicThis)
}
if (serverPrefetch) {
onServerPrefetch(serverPrefetch.bind(publicThis))
runLifecycleHook(onServerPrefetch, serverPrefetch, publicThis)
}

if (__COMPAT__) {
if (
beforeDestroy &&
softAssertCompatEnabled(DeprecationTypes.OPTIONS_BEFORE_DESTROY, instance)
) {
onBeforeUnmount(beforeDestroy.bind(publicThis))
runLifecycleHook(onBeforeUnmount, beforeDestroy, publicThis)
}
if (
destroyed &&
softAssertCompatEnabled(DeprecationTypes.OPTIONS_DESTROYED, instance)
) {
onUnmounted(destroyed.bind(publicThis))
runLifecycleHook(onUnmounted, destroyed, publicThis)
}
}

Expand All @@ -835,6 +835,12 @@ export function applyOptions(
}
}

function runLifecycleHook(handler: Function, hook: Function | Function[], context: ComponentPublicInstance) {
// Array lifecycle hooks are only present in the compat build
if (__COMPAT__ && isArray(hook)) hook.forEach(_hook => handler(_hook.bind(context)))
else handler((hook as Function).bind(context))
}

function resolveInstanceAssets(
instance: ComponentInternalInstance,
mixin: ComponentOptions,
Expand Down
4 changes: 4 additions & 0 deletions packages/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,7 @@ export const getGlobalThis = (): any => {
: {})
)
}

export const toArray = (target: any) => {
return isArray(target) ? target : target ? [target] : []
}
19 changes: 14 additions & 5 deletions packages/vue-compat/__tests__/global.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
35 changes: 34 additions & 1 deletion packages/vue-compat/__tests__/options.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ beforeEach(() => {
toggleDeprecationWarning(true)
Vue.configureCompat({
MODE: 2,
GLOBAL_MOUNT: 'suppress-warning'
GLOBAL_MOUNT: 'suppress-warning',
GLOBAL_EXTEND: 'suppress-warning'
})
})

Expand Down Expand Up @@ -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: `<child v-if="ok"/>`,
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()
})

0 comments on commit e52193d

Please sign in to comment.