diff --git a/src/core/instance/inject.js b/src/core/instance/inject.js index b0b9b5a69f9..c6bcd81cca2 100644 --- a/src/core/instance/inject.js +++ b/src/core/instance/inject.js @@ -14,11 +14,31 @@ export function initProvide (vm: Component) { } export function initInjections (vm: Component) { - const inject: any = vm.$options.inject + const result = resolveInject(vm.$options.inject, vm) + if (result) { + Object.keys(result).forEach(key => { + if (process.env.NODE_ENV !== 'production') { + defineReactive(vm, key, result[key], () => { + warn( + `Avoid mutating an injected value directly since the changes will be ` + + `overwritten whenever the provided component re-renders. ` + + `injection being mutated: "${key}"`, + vm + ) + }) + } else { + defineReactive(vm, key, result[key]) + } + }) + } +} + +export function resolveInject (inject: any, vm: Component): ?Object { if (inject) { // inject is :any because flow is not smart enough to figure out cached // isArray here const isArray = Array.isArray(inject) + const result = Object.create(null) const keys = isArray ? inject : hasSymbol @@ -49,5 +69,6 @@ export function initInjections (vm: Component) { source = source.$parent } } + return result } } diff --git a/src/core/vdom/create-component.js b/src/core/vdom/create-component.js index 7a79ee8ea71..c2a42c3f045 100644 --- a/src/core/vdom/create-component.js +++ b/src/core/vdom/create-component.js @@ -4,6 +4,7 @@ import VNode from './vnode' import { createElement } from './create-element' import { resolveConstructorOptions } from '../instance/init' import { resolveSlots } from '../instance/render-helpers/resolve-slots' +import { resolveInject } from '../instance/inject' import { warn, @@ -184,11 +185,15 @@ function createFunctionalComponent ( // gets a unique context - this is necessary for correct named slot check const _context = Object.create(context) const h = (a, b, c, d) => createElement(_context, a, b, c, d, true) + + // functional injections should not reactive + const injections = resolveInject(Ctor.options.inject, _context) const vnode = Ctor.options.render.call(null, h, { props, data, parent: context, children, + injections, slots: () => resolveSlots(children, context) }) if (vnode instanceof VNode) { diff --git a/test/unit/features/options/inject.spec.js b/test/unit/features/options/inject.spec.js index 2949e43c750..fdb69ea1c34 100644 --- a/test/unit/features/options/inject.spec.js +++ b/test/unit/features/options/inject.spec.js @@ -144,6 +144,29 @@ describe('Options provide/inject', () => { expect(child.baz).toBe(3) }) + // Github issue #5194 + it('should work with functional', () => { + new Vue({ + template: ``, + provide: { + foo: 1, + bar: false + }, + components: { + child: { + functional: true, + inject: ['foo', 'bar'], + render (h, context) { + const { injections } = context + injected = [injections.foo, injections.bar] + } + } + } + }).$mount() + + expect(injected).toEqual([1, false]) + }) + if (typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys)) { it('with Symbol keys', () => { const s = Symbol() diff --git a/types/options.d.ts b/types/options.d.ts index de1b20d995d..88dccec3739 100644 --- a/types/options.d.ts +++ b/types/options.d.ts @@ -69,6 +69,7 @@ export interface RenderContext { slots(): any; data: VNodeData; parent: Vue; + injections: any } export interface PropOptions {