also warn set/delete on instance root $data

This commit is contained in:
Evan You 2016-05-31 13:31:29 -04:00
parent 818faa7d58
commit 47f24430cb
5 changed files with 42 additions and 57 deletions

View File

@ -122,7 +122,7 @@ export function lifecycleMixin (Vue: Class<Component>) {
}
if (vm._watchers.length) {
for (let i = 0; i < vm._watchers.length; i++) {
vm._watchers[i].update()
vm._watchers[i].update(true /* shallow */)
}
}
}
@ -150,7 +150,7 @@ export function lifecycleMixin (Vue: Class<Component>) {
// remove reference from data ob
// frozen object may not have observer.
if (vm._data.__ob__) {
vm._data.__ob__.removeVm(vm)
vm._data.__ob__.vmCount--
}
// call the last hook...
vm._isDestroyed = true

View File

@ -62,7 +62,8 @@ function initData (vm: Component) {
proxy(vm, keys[i])
}
// observe data
observe(data, vm)
observe(data)
data.__ob__.vmCount++
}
const computedSharedDefinition = {
@ -205,7 +206,8 @@ function setData (vm: Component, newData: Object) {
proxy(vm, key)
}
}
oldData.__ob__.removeVm(vm)
observe(newData, vm)
oldData.__ob__.vmCount--
observe(newData)
newData.__ob__.vmCount++
vm.$forceUpdate()
}

View File

@ -5,7 +5,6 @@ import Dep from './dep'
import { arrayMethods } from './array'
import {
def,
remove,
isObject,
isPlainObject,
hasProto,
@ -35,12 +34,12 @@ export const observerState = {
export class Observer {
value: any;
dep: Dep;
vms: ?Array<Component>;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vms = null
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
const augment = hasProto
@ -73,24 +72,6 @@ export class Observer {
observe(items[i])
}
}
/**
* Add an owner vm, so that when $set/$delete mutations
* happen we can notify owner vms to proxy the keys and
* digest the watchers. This is only called when the object
* is observed as an instance's root $data.
*/
addVm (vm: Component) {
(this.vms || (this.vms = [])).push(vm)
}
/**
* Remove an owner vm. This is called when the object is
* swapped out as an instance's $data object.
*/
removeVm (vm: Component) {
remove(this.vms, vm)
}
}
// helpers
@ -123,7 +104,7 @@ function copyAugment (target: Object, src: Object, keys: Array<string>) {
* returns the new observer if successfully observed,
* or the existing observer if the value already has one.
*/
export function observe (value: any, vm?: Component): Observer | void {
export function observe (value: any): Observer | void {
if (!isObject(value)) {
return
}
@ -139,9 +120,6 @@ export function observe (value: any, vm?: Component): Observer | void {
) {
ob = new Observer(value)
}
if (ob && vm) {
ob.addVm(vm)
}
return ob
}
@ -210,28 +188,20 @@ export function set (obj: Array<any> | Object, key: any, val: any) {
obj[key] = val
return
}
if (obj._isVue) {
const ob = obj.__ob__
if (obj._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Do not add reactive properties to a Vue instance at runtime - ' +
'delcare it upfront in the data option.'
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - delcare it upfront in the data option.'
)
return
}
const ob = obj.__ob__
if (!ob) {
obj[key] = val
return
}
defineReactive(ob.value, key, val)
ob.dep.notify()
if (ob.vms) {
let i = ob.vms.length
while (i--) {
const vm = ob.vms[i]
proxy(vm, key)
vm.$forceUpdate()
}
}
return val
}
@ -239,9 +209,11 @@ export function set (obj: Array<any> | Object, key: any, val: any) {
* Delete a property and trigger change if necessary.
*/
export function del (obj: Object, key: string) {
if (obj._isVue) {
const ob = obj.__ob__
if (obj._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Do not delete properties on a Vue instance - just set it to null.'
'Avoid deleting properties on a Vue instance or its root $data ' +
'- just set it to null.'
)
return
}
@ -249,19 +221,10 @@ export function del (obj: Object, key: string) {
return
}
delete obj[key]
const ob = obj.__ob__
if (!ob) {
return
}
ob.dep.notify()
if (ob.vms) {
let i = ob.vms.length
while (i--) {
const vm = ob.vms[i]
unproxy(vm, key)
vm.$forceUpdate()
}
}
}
export function proxy (vm: Component, key: string) {

View File

@ -60,7 +60,7 @@ describe('Instance methods lifecycle', () => {
it('remove self from data observer', () => {
const vm = new Vue({ data: { a: 1 }})
vm.$destroy()
expect(vm.$data.__ob__.vms.length).toBe(0)
expect(vm.$data.__ob__.vmCount).toBe(0)
})
it('avoid duplicate calls', () => {

View File

@ -290,13 +290,33 @@ describe('Observer', () => {
Vue.set(vm, 'a', 2)
waitForUpdate(() => {
expect(vm.$el.outerHTML).toBe('<div>2</div>')
expect('Do not add reactive properties to a Vue instance').not.toHaveBeenWarned()
expect('Avoid adding reactive properties to a Vue instance').not.toHaveBeenWarned()
Vue.delete(vm, 'a')
}).then(() => {
expect('Do not delete properties on a Vue instance').toHaveBeenWarned()
expect('Avoid deleting properties on a Vue instance').toHaveBeenWarned()
expect(vm.$el.outerHTML).toBe('<div>2</div>')
Vue.set(vm, 'b', 123)
expect('Do not add reactive properties to a Vue instance').toHaveBeenWarned()
expect('Avoid adding reactive properties to a Vue instance').toHaveBeenWarned()
}).then(done)
})
it('warning set/delete on Vue instance root $data', done => {
const data = { a: 1 }
const vm = new Vue({
template: '<div>{{a}}</div>',
data
}).$mount()
expect(vm.$el.outerHTML).toBe('<div>1</div>')
Vue.set(data, 'a', 2)
waitForUpdate(() => {
expect(vm.$el.outerHTML).toBe('<div>2</div>')
expect('Avoid adding reactive properties to a Vue instance').not.toHaveBeenWarned()
Vue.delete(data, 'a')
}).then(() => {
expect('Avoid deleting properties on a Vue instance').toHaveBeenWarned()
expect(vm.$el.outerHTML).toBe('<div>2</div>')
Vue.set(data, 'b', 123)
expect('Avoid adding reactive properties to a Vue instance').toHaveBeenWarned()
}).then(done)
})