Skip to content

Commit 7fa8fa7

Browse files
committed
avoid duplicate lifecycle hooks during constructor resolution
1 parent 673acec commit 7fa8fa7

File tree

2 files changed

+41
-14
lines changed

2 files changed

+41
-14
lines changed

src/core/instance/init.js

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ function initInternalComponent (vm: Component, options: InternalComponentOptions
8686
export function resolveConstructorOptions (Ctor: Class<Component>) {
8787
let options = Ctor.options
8888
if (Ctor.super) {
89-
const superOptions = Ctor.super.options
89+
const superOptions = resolveConstructorOptions(Ctor.super)
9090
const cachedSuperOptions = Ctor.superOptions
9191
if (superOptions !== cachedSuperOptions) {
9292
// super option changed,
@@ -108,14 +108,31 @@ export function resolveConstructorOptions (Ctor: Class<Component>) {
108108
}
109109

110110
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
111-
let res
112-
const options = Ctor.options
111+
let modified
112+
const latest = Ctor.options
113113
const sealed = Ctor.sealedOptions
114-
for (const key in options) {
115-
if (sealed[key] !== options[key]) {
116-
if (!res) res = {}
117-
res[key] = options[key]
114+
for (const key in latest) {
115+
if (latest[key] !== sealed[key]) {
116+
if (!modified) modified = {}
117+
modified[key] = dedupe(latest[key], sealed[key])
118118
}
119119
}
120-
return res
120+
return modified
121+
}
122+
123+
function dedupe (latest, sealed) {
124+
// compare latest and sealed to ensure lifecycle hooks won't be duplicated
125+
// between merges
126+
if (Array.isArray(latest)) {
127+
const res = []
128+
sealed = Array.isArray(sealed) ? sealed : [sealed]
129+
for (let i = 0; i < latest.length; i++) {
130+
if (sealed.indexOf(latest[i]) < 0) {
131+
res.push(latest[i])
132+
}
133+
}
134+
return res
135+
} else {
136+
return latest
137+
}
121138
}

test/unit/features/global-api/mixin.spec.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,31 +87,41 @@ describe('Global API: mixin', () => {
8787

8888
// #4976
8989
it('should not drop late-attached custom options on existing constructors', () => {
90-
const Test = Vue.extend({})
90+
const baseSpy = jasmine.createSpy('base')
91+
const Base = Vue.extend({
92+
beforeCreate: baseSpy
93+
})
94+
95+
const Test = Base.extend({})
9196

9297
// Inject options later
9398
// vue-loader and vue-hot-reload-api are doing like this
9499
Test.options.computed = {
95100
$style: () => 123
96101
}
97102

98-
const spy = jasmine.createSpy('mixin')
99-
Test.options.beforeCreate = [spy]
103+
const spy = jasmine.createSpy('late attached')
104+
Test.options.beforeCreate = Test.options.beforeCreate.concat(spy)
100105

101106
// Update super constructor's options
102-
Vue.mixin({})
107+
const mixinSpy = jasmine.createSpy('mixin')
108+
Vue.mixin({
109+
beforeCreate: mixinSpy
110+
})
103111

104112
// mount the component
105113
const vm = new Test({
106114
template: '<div>{{ $style }}</div>'
107115
}).$mount()
108116

109-
expect(spy).toHaveBeenCalled()
117+
expect(spy.calls.count()).toBe(1)
118+
expect(baseSpy.calls.count()).toBe(1)
119+
expect(mixinSpy.calls.count()).toBe(1)
110120
expect(vm.$el.textContent).toBe('123')
111121
expect(vm.$style).toBe(123)
112122

113123
// Should not be dropped
114124
expect(Test.options.computed.$style()).toBe(123)
115-
expect(Test.options.beforeCreate).toEqual([spy])
125+
expect(Test.options.beforeCreate).toEqual([mixinSpy, baseSpy, spy])
116126
})
117127
})

0 commit comments

Comments
 (0)