Przeglądaj źródła

fix(core): dedupe lifecycle hooks during options merge

The fix landed in #9199 causes further extended constructors used as
mixins to drop options from its inheritance chain, so a different fix
is needed.
Evan You 7 lat temu
rodzic
commit
edf7df0c83

+ 1 - 21
src/core/instance/init.js

@@ -117,32 +117,12 @@ export function resolveConstructorOptions (Ctor: Class<Component>) {
 function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
   let modified
   const latest = Ctor.options
-  const extended = Ctor.extendOptions
   const sealed = Ctor.sealedOptions
   for (const key in latest) {
     if (latest[key] !== sealed[key]) {
       if (!modified) modified = {}
-      modified[key] = dedupe(latest[key], extended[key], sealed[key])
+      modified[key] = latest[key]
     }
   }
   return modified
 }
-
-function dedupe (latest, extended, sealed) {
-  // compare latest and sealed to ensure lifecycle hooks won't be duplicated
-  // between merges
-  if (Array.isArray(latest)) {
-    const res = []
-    sealed = Array.isArray(sealed) ? sealed : [sealed]
-    extended = Array.isArray(extended) ? extended : [extended]
-    for (let i = 0; i < latest.length; i++) {
-      // push original options and not sealed options to exclude duplicated options
-      if (extended.indexOf(latest[i]) >= 0 || sealed.indexOf(latest[i]) < 0) {
-        res.push(latest[i])
-      }
-    }
-    return res
-  } else {
-    return latest
-  }
-}

+ 15 - 2
src/core/util/options.js

@@ -147,13 +147,26 @@ function mergeHook (
   parentVal: ?Array<Function>,
   childVal: ?Function | ?Array<Function>
 ): ?Array<Function> {
-  return childVal
+  const res = childVal
     ? parentVal
       ? parentVal.concat(childVal)
       : Array.isArray(childVal)
         ? childVal
         : [childVal]
     : parentVal
+  return res
+    ? dedupeHooks(res)
+    : res
+}
+
+function dedupeHooks (hooks) {
+  const res = []
+  for (let i = 0; i < hooks.length; i++) {
+    if (res.indexOf(hooks[i]) === -1) {
+      res.push(hooks[i])
+    }
+  }
+  return res
 }
 
 LIFECYCLE_HOOKS.forEach(hook => {
@@ -382,7 +395,7 @@ export function mergeOptions (
   }
 
   if (typeof child === 'function') {
-    child = child.extendOptions
+    child = child.options
   }
 
   normalizeProps(child, vm)

+ 27 - 0
test/unit/features/global-api/mixin.spec.js

@@ -167,4 +167,31 @@ describe('Global API: mixin', () => {
   it('chain call', () => {
     expect(Vue.mixin({})).toBe(Vue)
   })
+
+  // #9198
+  it('should not mix global mixin lifecycle hook twice', () => {
+    const spy = jasmine.createSpy('global mixed in lifecycle hook')
+    Vue.mixin({
+      created: spy
+    })
+
+    const mixin1 = Vue.extend({
+      methods: {
+        a() {}
+      }
+    })
+
+    const mixin2 = Vue.extend({
+      mixins: [mixin1]
+    })
+
+    const Child = Vue.extend({
+      mixins: [mixin2],
+    })
+
+    const vm = new Child()
+
+    expect(typeof vm.$options.methods.a).toBe('function')
+    expect(spy.calls.count()).toBe(1)
+  })
 })

+ 21 - 17
test/unit/features/options/mixins.spec.js

@@ -110,31 +110,35 @@ describe('Options mixins', () => {
     expect(vm.$options.directives.c).toBeDefined()
   })
 
-  it('should not mix global mixined lifecycle hook twice', () => {
-    const spy = jasmine.createSpy('global mixed in lifecycle hook')
-    Vue.mixin({
-      created() {
-        spy()
-      }
-    })
+  it('should accept further extended constructors as mixins', () => {
+    const spy1 = jasmine.createSpy('mixinA')
+    const spy2 = jasmine.createSpy('mixinB')
 
-    const mixin1 = Vue.extend({
+    const mixinA = Vue.extend({
+      created: spy1,
+      directives: {
+        c: {}
+      },
       methods: {
-        a() {}
+        a: function () {}
       }
     })
 
-    const mixin2 = Vue.extend({
-      mixins: [mixin1],
+    const mixinB = mixinA.extend({
+      created: spy2
     })
 
-    const Child = Vue.extend({
-      mixins: [mixin2],
+    const vm = new Vue({
+      mixins: [mixinB],
+      methods: {
+        b: function () {}
+      }
     })
 
-    const vm = new Child()
-
-    expect(typeof vm.$options.methods.a).toBe('function')
-    expect(spy.calls.count()).toBe(1)
+    expect(spy1).toHaveBeenCalledTimes(1)
+    expect(spy2).toHaveBeenCalledTimes(1)
+    expect(vm.a).toBeDefined()
+    expect(vm.b).toBeDefined()
+    expect(vm.$options.directives.c).toBeDefined()
   })
 })