]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compat): correctly merge lifecycle hooks when using Vue.extend (#3762)
authorStanislav Lashmanov <stasvarenkin@gmail.com>
Wed, 12 May 2021 21:13:44 +0000 (00:13 +0300)
committerGitHub <noreply@github.com>
Wed, 12 May 2021 21:13:44 +0000 (17:13 -0400)
fix #3761

packages/runtime-core/src/compat/globalConfig.ts
packages/runtime-core/src/componentOptions.ts
packages/vue-compat/__tests__/global.spec.ts
packages/vue-compat/__tests__/options.spec.ts

index bf7b9189e077d717d56b597de80484299f41c281..593cd20e31beb47e7d80d9b7293d608b38c6c3ca 100644 (file)
@@ -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) {
index 40f5669b508003fa8f067828c5a24d888e46bb4d..0fd25bc7156b6b0400916dc7f69fe0d857c4f481 100644 (file)
@@ -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)
     }
   }
 
index 02d578772b79e9a02183b470e7db403bbf992fe6..6ef28f1ddbfd33688477e380691b875c3446f2c7 100644 (file)
@@ -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', () => {
index ca8ea80738100815f6669297d8d06e63b4f349c5..d0225dc964b66de346ae49b711de469fb113fd29 100644 (file)
@@ -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: `<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()
+})