# 注意对象是否已经被侦测
# 采坑回顾
项目中定义了一份配置文件作为两个组件data的初始值
// config.js
export default {
a: {
aa: 1,
},
};
然后分别在两个组件First,Two中引用:
// First
<template>
<div>First {{ first }}</div>
</template>
<script>
import config from '@/config';
export default {
data() {
return {
first: {
config,
},
};
},
};
</script>
// Two
<template>
<div>
<div @click="handleClick">Two {{ two }}</div>
</div>
</template>
<script>
import config from '@/config';
export default {
data() {
return {
two: {},
};
},
methods: {
handleClick() {
this.two.config.a.bb = 3;
},
},
mounted() {
config.a.bb = 2;
this.$set(this.two, 'config', config);
},
};
</script>
我们在Two这个组件中对引入的config添加了一个属性,打算通过$set统一设置响应式
我们将两个组件引用到Home组件中
<template>
<div class="home">
<First></First>
<Two></Two>
</div>
</template>
<script>
import First from '@/components/First';
import Two from '@/components/Two';
export default {
components: { First, Two },
};
</script>
此时我们clickTwo组件时数据没有发生变化,查看具体的data发现后添加的bb没有被纳入响应式

我们就觉得奇怪,按理说this.$set(this.two, 'config', config);应该会递归将所有属性都变成响应式的getter和setter才对,为什么没有生效呢?
后来发现,原来在First组件中,config对象已经被observe过一次了,于是在Two组件中就不会被再次observe,所以实际上我们的this.$set没有生效。
# 核心知识
observe方法中会判断要设置的对象是否存在__ob__,如果存在则说明已经被纳入侦测,就直接返回__ob__。这实际上是为了防止重复侦测,提高效率,但是我们在使用的时候也要注意预防这一点带来的坑。
export function observe (value: any, asRootData: ?boolean): Observer | void {
...
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} ...
return ob
}
# 解决方案
克隆目标对象。
不论是prop还是模块,我们最好都以只读形式对待,这样既可以保证数据的单向流动性,又能做到互相隔离。