Skip to content

Commit e3e25ff

Browse files
committed
fix(core): injections should not always reactive #(5913)
fix vuejs#5913
1 parent 0d6ad12 commit e3e25ff

File tree

4 files changed

+116
-33
lines changed

4 files changed

+116
-33
lines changed

src/core/instance/inject.js

+21-11
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/* @flow */
22

33
import { hasSymbol } from 'core/util/env'
4-
import { warn } from '../util/index'
5-
import { defineReactive } from '../observer/index'
4+
import { warn, defWithGetterSetter } from '../util/index'
5+
import { defineReactive, isObserver } from '../observer/index'
66
import { hasOwn } from 'shared/util'
77

88
export function initProvide (vm: Component) {
@@ -18,18 +18,28 @@ export function initInjections (vm: Component) {
1818
const result = resolveInject(vm.$options.inject, vm)
1919
if (result) {
2020
Object.keys(result).forEach(key => {
21+
const value = result[key]
22+
const warnSetter = () => {
23+
warn(
24+
`Avoid mutating an injected value directly since the changes will be ` +
25+
`overwritten whenever the provided component re-renders. ` +
26+
`injection being mutated: "${key}"`,
27+
vm
28+
)
29+
}
2130
/* istanbul ignore else */
2231
if (process.env.NODE_ENV !== 'production') {
23-
defineReactive(vm, key, result[key], () => {
24-
warn(
25-
`Avoid mutating an injected value directly since the changes will be ` +
26-
`overwritten whenever the provided component re-renders. ` +
27-
`injection being mutated: "${key}"`,
28-
vm
29-
)
30-
})
32+
if (isObserver(value)) {
33+
defineReactive(vm, key, value, warnSetter)
34+
} else {
35+
defWithGetterSetter(vm, key, value, warnSetter)
36+
}
3137
} else {
32-
defineReactive(vm, key, result[key])
38+
if (isObserver(value)) {
39+
defineReactive(vm, key, value)
40+
} else {
41+
defWithGetterSetter(vm, key, value)
42+
}
3343
}
3444
})
3545
}

src/core/observer/index.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export function observe (value: any, asRootData: ?boolean): Observer | void {
109109
return
110110
}
111111
let ob: Observer | void
112-
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
112+
if (isObserver(value)) {
113113
ob = value.__ob__
114114
} else if (
115115
observerState.shouldConvert &&
@@ -255,3 +255,10 @@ function dependArray (value: Array<any>) {
255255
}
256256
}
257257
}
258+
259+
export function isObserver (obj: any): boolean {
260+
if (isObject(obj)) {
261+
return hasOwn(obj, '__ob__') && obj.__ob__ instanceof Observer
262+
}
263+
return false
264+
}

src/core/util/lang.js

+20
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,26 @@ export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
2222
})
2323
}
2424

25+
/**
26+
* Define a property with getter and setter.
27+
*/
28+
export function defWithGetterSetter (obj: Object, key: string, val: any, customSetter?: Function) {
29+
Object.defineProperty(obj, key, {
30+
enumerable: true,
31+
configurable: true,
32+
get: function getter () {
33+
return val
34+
},
35+
set: function setter (newVal) {
36+
/* eslint-enable no-self-compare */
37+
if (process.env.NODE_ENV !== 'production' && customSetter) {
38+
customSetter()
39+
}
40+
val = newVal
41+
}
42+
})
43+
}
44+
2545
/**
2646
* Parse simple path.
2747
*/

test/unit/features/options/inject.spec.js

+67-21
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Vue from 'vue'
22
import { isNative } from 'core/util/env'
3+
import { isObserver } from 'core/observer'
34

45
describe('Options provide/inject', () => {
56
let injected
@@ -186,40 +187,78 @@ describe('Options provide/inject', () => {
186187
})
187188
}
188189

189-
// Github issue #5223
190-
it('should work with reactive array', done => {
190+
it('should work when the provide change', done => {
191191
const vm = new Vue({
192192
template: `<div><child></child></div>`,
193193
data () {
194194
return {
195-
foo: []
195+
foo: 0,
196+
bar: {
197+
val: 0
198+
},
199+
baz: []
196200
}
197201
},
198202
provide () {
199203
return {
200-
foo: this.foo
204+
foo: this.foo,
205+
bar: this.bar,
206+
baz: this.baz
201207
}
202208
},
203209
components: {
204210
child: {
205-
inject: ['foo'],
206-
template: `<span>{{foo.length}}</span>`
211+
inject: ['foo', 'bar', 'baz'],
212+
template: `<span>{{foo}},{{bar.val}},{{baz.length}}</span>`
207213
}
208214
}
209215
}).$mount()
210216

211-
expect(vm.$el.innerHTML).toEqual(`<span>0</span>`)
212-
vm.foo.push(vm.foo.length)
217+
expect(vm.$el.innerHTML).toEqual(`<span>0,0,0</span>`)
218+
vm.foo = 1 // primitive should no modified
219+
vm.bar.val = 1 // reactive should modified
220+
vm.baz.push(0) // reactive array should modified
213221
vm.$nextTick(() => {
214-
expect(vm.$el.innerHTML).toEqual(`<span>1</span>`)
215-
vm.foo.pop()
216-
vm.$nextTick(() => {
217-
expect(vm.$el.innerHTML).toEqual(`<span>0</span>`)
218-
done()
219-
})
222+
expect(vm.$el.innerHTML).toEqual(`<span>0,1,1</span>`)
223+
done()
220224
})
221225
})
222226

227+
// Github issue #5913
228+
it('should keep the reactive with provide', () => {
229+
const vm = new Vue({
230+
template: `<div><child ref='child'></child></div>`,
231+
data () {
232+
return {
233+
foo: {},
234+
$foo: {},
235+
foo1: []
236+
}
237+
},
238+
provide () {
239+
return {
240+
foo: this.foo,
241+
$foo: this.$foo,
242+
foo1: this.foo1,
243+
bar: {},
244+
baz: []
245+
}
246+
},
247+
components: {
248+
child: {
249+
inject: ['foo', '$foo', 'foo1', 'bar', 'baz'],
250+
template: `<span/>`
251+
}
252+
}
253+
}).$mount()
254+
const child = vm.$refs.child
255+
expect(isObserver(child.foo)).toBe(true)
256+
expect(isObserver(child.$foo)).toBe(false)
257+
expect(isObserver(child.foo1)).toBe(true)
258+
expect(isObserver(child.bar)).toBe(false)
259+
expect(isObserver(child.baz)).toBe(false)
260+
})
261+
223262
it('should extend properly', () => {
224263
const parent = Vue.extend({
225264
template: `<span/>`,
@@ -250,24 +289,31 @@ describe('Options provide/inject', () => {
250289
})
251290

252291
it('should warn when injections has been modified', () => {
253-
const key = 'foo'
292+
const makeWarnText = key =>
293+
`Avoid mutating an injected value directly since the changes will be ` +
294+
`overwritten whenever the provided component re-renders. ` +
295+
`injection being mutated: "${key}"`
296+
254297
const vm = new Vue({
255298
provide: {
256-
foo: 1
299+
foo: 1,
300+
bar: {
301+
val: 1
302+
}
257303
}
258304
})
259305

260306
const child = new Vue({
261307
parent: vm,
262-
inject: ['foo']
308+
inject: ['foo', 'bar']
263309
})
264310

265311
expect(child.foo).toBe(1)
312+
expect(child.bar.val).toBe(1)
266313
child.foo = 2
267-
expect(
268-
`Avoid mutating an injected value directly since the changes will be ` +
269-
`overwritten whenever the provided component re-renders. ` +
270-
`injection being mutated: "${key}"`).toHaveBeenWarned()
314+
expect(makeWarnText('foo')).toHaveBeenWarned()
315+
child.bar = { val: 2 }
316+
expect(makeWarnText('bar')).toHaveBeenWarned()
271317
})
272318

273319
it('should warn when injections cannot be found', () => {

0 commit comments

Comments
 (0)