diff --git a/src/core/util/options.ts b/src/core/util/options.ts index ef6a10342a9..4c9cfbb208d 100644 --- a/src/core/util/options.ts +++ b/src/core/util/options.ts @@ -264,18 +264,68 @@ strats.props = return ret } +const reactiveInjectKey = '__reactiveInject__' + +function mergeReactiveInject( + target: Record, + parentProvide: Record | null | undefined, + childProvide: Record | null | undefined +) { + const parentInject = + parentProvide && typeof parentProvide === 'object' + ? parentProvide[reactiveInjectKey] + : null + const childInject = + childProvide && typeof childProvide === 'object' + ? childProvide[reactiveInjectKey] + : null + + if (!parentInject && !childInject) return + + const baseInject = + (parentInject && Object.getPrototypeOf(parentInject)) || + (childInject && Object.getPrototypeOf(childInject)) || + {} + const mergedInject = Object.create(baseInject) + + copyReactiveInject(mergedInject, parentInject) + copyReactiveInject(mergedInject, childInject) + + target[reactiveInjectKey] = mergedInject +} + +function copyReactiveInject( + target: Record, + source: Record | null | undefined +) { + if (!source || typeof source !== 'object') return + const keys = hasSymbol ? Reflect.ownKeys(source) : Object.keys(source) + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + if (key === '__ob__') continue + const desc = Object.getOwnPropertyDescriptor(source, key) + if (desc) { + Object.defineProperty(target, key, desc) + } + } +} + strats.provide = function (parentVal: Object | null, childVal: Object | null) { if (!parentVal) return childVal return function () { const ret = Object.create(null) - mergeData(ret, isFunction(parentVal) ? parentVal.call(this) : parentVal) + const parentProvide = isFunction(parentVal) ? parentVal.call(this) : parentVal + mergeData(ret, parentProvide) + let childProvide if (childVal) { + childProvide = isFunction(childVal) ? childVal.call(this) : childVal mergeData( ret, - isFunction(childVal) ? childVal.call(this) : childVal, + childProvide, false // non-recursive ) } + mergeReactiveInject(ret, parentProvide, childProvide) return ret } } diff --git a/test/unit/features/options/inject.spec.ts b/test/unit/features/options/inject.spec.ts index b4c295e3f51..a694cd55cd8 100644 --- a/test/unit/features/options/inject.spec.ts +++ b/test/unit/features/options/inject.spec.ts @@ -503,6 +503,70 @@ describe('Options provide/inject', () => { expect(injected).toEqual(['foo', 'bar']) }) + // #13005 + it('should merge reactive inject from mixins', () => { + const reactiveInjectKey = '__reactiveInject__' + + const mixinA = { + data() { + return { + serviceA: 'A' + } + }, + provide() { + const provided = Object.create(null) + provided[reactiveInjectKey] = Object.create( + this[reactiveInjectKey] || {} + ) + Object.defineProperty(provided[reactiveInjectKey], 'serviceA', { + enumerable: true, + configurable: true, + get: () => this.serviceA + }) + provided.serviceA = this.serviceA + return provided + } + } + + const mixinB = { + data() { + return { + serviceB: 'B' + } + }, + provide() { + const provided = Object.create(null) + provided[reactiveInjectKey] = Object.create( + this[reactiveInjectKey] || {} + ) + Object.defineProperty(provided[reactiveInjectKey], 'serviceB', { + enumerable: true, + configurable: true, + get: () => this.serviceB + }) + provided.serviceB = this.serviceB + return provided + } + } + + const child = { + inject: { reactive: reactiveInjectKey }, + render() {}, + created() { + injected = [this.reactive.serviceA, this.reactive.serviceB] + } + } + + new Vue({ + mixins: [mixinA, mixinB], + render(h) { + return h(child) + } + }).$mount() + + expect(injected).toEqual(['A', 'B']) + }) + it('should merge provide from mixins (mix of objects and functions)', () => { const mixinA = { provide: { foo: 'foo' } } const mixinB = { provide: () => ({ bar: 'bar' }) }