直接抛结论
直接修改第一层级的props会出现警告。如下面不管props是基础类型还是复杂类型,子组件中对其直接修改this.str = xxx, this.obj = xxx都是会警告的。 但修改更深层级,即props是复杂类型,直接修改其中的某个属性是不会出现警告的。例如props是obj: {a: 'i am obj.a'},子组件中直接修改obj.a(深层级)this.obj.a = 'new aaa'是不会出现警告的。
其实是与props递归响应式监听有关。
具体看下面的例子:
// 父组件
<template>
<child :str="str" :obj="obj" />
</template>
<script>
export default {
data: {
str: "i am string",
obj: {
a: "i am obj.a"
}
},
components: {child: () => import('./child')}
}
</script>
// 子组件
<template>
<div>
<div>str: {{str}}</div>
<div>obj: {{obj.a}}</div>
<div @click="changeStr">改变str</div>
<div @click="changeObj">改变obj</div>
</div>
</template>
<script>
export default {
props: ['str', 'obj'],
methods: {
changeStr() {
this.str = 'new str' //出现警告
},
changeObj() {
this.obj = { a: 'new aaa' } // 出现警告
},
},
}
</script>
上面例子中, 不管props是基础类型还是object,我们直接修改它都是会出现警告的
changeObj() {
this.obj.a = 'new aaa' // 不出现警告
},
看源码研究下这是为啥呢
vue在initProps时, 在非生产环境下,defineReactive会收到第四个参数(警告函数)。
isUpdatingChildComponent 默认是false, 当父组件修改时,会触发prepatch,会把isUpdatingChildComponent修改为true(所以通过父组件修改props,警告函数也会执行但什么也不输出)。
function initProps(vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {}
const props = vm._props = {}
const isRoot = !vm.$parent
for (const key in propsOptions) {
const value = validateProp(key, propsOptions, propsData, vm) // 验证 prop
if (process.env.NODE_ENV !== 'production') {
defineReactive(props, key, value, () => {
if (!isRoot && !isUpdatingChildComponent) {
warn(
`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
`Instead, use a data or computed property based on the prop's ` +
`value. Prop being mutated: "${key}"`,
vm
)
}
})
}
}
}
defineReactive 在进行defineProperty时,如果 val 是对象的话进行递归监听。而此时是用obeseve去监听的。
也就是上面例子中porps.obj又是对象,此时会对obj使用observe去监听。而observe监听方式不会传第四个参数(警告函数), 所以直接修改obj.a不会出现警告this.obj.a = "new obj.a"。
其实递归监听与监听data是一样的
function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
val = obj[key]
// 如果 val 是对象的话递归监听
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
........
})
下面是对data监听过程, 可以发现defineReactive时候是不会传第四个参数(警告函数)的
- initData
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// observe data
observe(data, true /* asRootData */)
}
- observe
export function observe (value: any, asRootData: ?boolean): Observer | void {
let ob: Observer | void
ob = new Observer(value) // 创建一个监听者
return ob
}
- new Observer
class Observer {
value: any;
dep: Dep;
constructor (value: any) {
this.value = value
this.dep = new Dep()
// 判断数组是否有原型 在该处重写数组的一些方法,因为 Object.defineProperty 函数
// 对于数组的数据变化支持的不好,
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* 监听所有属性。当值类型为Object时才应调用此方法
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* 观测数组中的每个元素
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}