Skip to content

Commit

Permalink
fix(core): dedupe lifecycle hooks during options merge
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
yyx990803 committed Jan 11, 2019
1 parent 17d8bcb commit edf7df0
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 40 deletions.
22 changes: 1 addition & 21 deletions src/core/instance/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
17 changes: 15 additions & 2 deletions src/core/util/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down Expand Up @@ -382,7 +395,7 @@ export function mergeOptions (
}

if (typeof child === 'function') {
child = child.extendOptions
child = child.options
}

normalizeProps(child, vm)
Expand Down
27 changes: 27 additions & 0 deletions test/unit/features/global-api/mixin.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
})
38 changes: 21 additions & 17 deletions test/unit/features/options/mixins.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
})
})

0 comments on commit edf7df0

Please sign in to comment.