递归监听
默认情况下,Vue3 中的 ref 和 reactive 都是递归监听的(层级深的对象),即能实时监听对象的底层变化。
例如,在 ref 中
<template>
<div>
<p>msg.a.b.c = {{msg.a.b.c}}</p>
<p>msg.e.f = {{msg.e.f}}</p>
<p>msg.g = {{msg.g}}</p>
<button @click="c">button</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'App',
setup() {
let msg = ref({
a: {
b: {
c: 'c'
}
},
e: {
f: 'f'
},
g: 'g'
});
function c() {
console.log(msg);
msg.value.a.b.c = 'C';
msg.value.e.f = 'F';
msg.value.g = 'G';
};
return {
msg,
c
};
}
}
</script>

点击 button

reactive递归
<template>
<div>
<p>msg.a.b.c = {{msg.a.b.c}}</p>
<p>msg.e.f = {{msg.e.f}}</p>
<p>msg.g = {{msg.g}}</p>
<button @click="c">button</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'App',
setup() {
let msg = reactive({
a: {
b: {
c: 'c'
}
},
e: {
f: 'f'
},
g: 'g'
});
function c() {
console.log(msg);
msg.a.b.c = 'C';
msg.e.f = 'F';
msg.g = 'G';
};
return {
msg,
c
};
}
}
</script>
在 reactive 中也是类似的。总之,就是只要我们对 ref 和 reactive 中的内容进行更改,都是能察觉到并且进行双向数据绑定的。
在默认情况下,递归监听肯定是好的,它让数据的变化能被实时监测到。然而它也带来了性能消耗的问题。
Vue3 提供了 shallow 方案,以防止进行递归式的监听。
非递归监听
1.递归监听存在的问题 如果数据量比较大, 非常消耗性能 2.非递归监听 shallowRef / shallowReactive 3.如何触发非递归监听属性更新界面? 如果是shallowRef类型数据, 可以通过triggerRef来触发
4.应用场景 一般情况下我们使用 ref和reactive即可 只有在需要监听的数据量比较大的时候, 我们才使用shallowRef/shallowReactive
#shallow
#shallowRef
shallow 式的创建 ref 需要使用一个新的 api,shallowRef。
尝试用 shallowRef 对我们一开始的示例进行改造。
<template>
<div>
<p>msg.a.b.c = {{msg.a.b.c}}</p>
<p>msg.e.f = {{msg.e.f}}</p>
<p>msg.g = {{msg.g}}</p>
<button @click="c">button</button>
</div>
</template>
<script>
import { ref, shallowRef } from 'vue'
export default {
name: 'App',
setup() {
let msg = shallowRef({
a: {
b: {
c: 'c'
}
},
e: {
f: 'f'
},
g: 'g'
});
function c() {
console.log(msg);
msg.value.a.b.c = 'C';
msg.value.e.f = 'F';
msg.value.g = 'G';
};
return {
msg,
c
};
}
}
</script>
此时我们再点击 button ,会发现控制台提示了数据的改变,但并没有实现对界面的数据绑定。

shallow类型的数据,只会监听最外层的数据的变化,才会引起视图层的变化,此时shaollowRef类型最外层的数据是value(并不是.g),所以只有在直接改变 msg.value 的时候才会产生监测,
例如
<template>
<div>
<p>msg.a.b.c = {{msg.a.b.c}}</p>
<p>msg.e.f = {{msg.e.f}}</p>
<p>msg.g = {{msg.g}}</p>
<button @click="c">button</button>
</div>
</template>
<script>
import { ref, shallowRef } from 'vue'
export default {
name: 'App',
setup() {
let msg = shallowRef({
a: {
b: {
c: 'c'
}
},
e: {
f: 'f'
},
g: 'g'
});
function c() {
console.log(msg);
// msg.value.a.b.c = 'C';
// msg.value.e.f = 'F';
// msg.value.g = 'G';
msg.value = {
a: {
b: {
c: 'C'
}
},
e: {
f: 'F'
},
g: 'G'
}
console.log(msg);
};
return {
msg,
c
};
}
}
</script>
此时最外层value的数据改变(被监听到),视图ui才会发生变化

#triggerRef
除此之外,对于 shallow 过的 ref 对象,我们还可以手动去触发 ref 的变化监听来实现界面的改变。
使用的 api 是 triggerRef
<template>
<div>
<p>msg.a.b.c = {{msg.a.b.c}}</p>
<p>msg.e.f = {{msg.e.f}}</p>
<p>msg.g = {{msg.g}}</p>
<button @click="c">button</button>
</div>
</template>
<script>
import { ref, shallowRef, triggerRef } from 'vue'
export default {
name: 'App',
setup() {
let msg = shallowRef({
a: {
b: {
c: 'c'
}
},
e: {
f: 'f'
},
g: 'g'
});
function c() {
console.log(msg);
msg.value.a.b.c = 'C';
msg.value.e.f = 'F';
msg.value.g = 'G';
triggerRef(msg);
console.log(msg);
};
return {
msg,
c
};
}
}
</script>
此时不需要更改最外层value的数据,内层的数据发生的变化,也同样被监听到,触发视图跟新
#shallowReactive
同样的,我们还有 shallowReactive 来实现类似 shallowRef 的功能。
<template>
<div>
<p>msg.a.b.c = {{msg.a.b.c}}</p>
<p>msg.e.f = {{msg.e.f}}</p>
<p>msg.g = {{msg.g}}</p>
<button @click="c">button</button>
</div>
</template>
<script>
import { shallowReactive } from 'vue'
export default {
name: 'App',
setup() {
let msg = shallowReactive({
a: {
b: {
c: 'c'
}
},
e: {
f: 'f'
},
g: 'g'
});
function c() {
console.log(msg);
msg.a.b.c = 'C';
msg.e.f = 'F';
msg.g = 'G';
console.log(msg);
};
return {
msg,
c
};
}
}
</script>
但如果你有进行实践的话会发现,这段代码仍然会允许你在点击 button 的时候对界面 UI 进行改变。

原因很简单,就是我在上文提到的,shallow 会监测最外层的变化而请求更新视图层,之前在 shallowRef 中的最外层是 value ,所以我们只能改变整个 value 值来提醒变化,而这里 shallowReactive 的最外层变成了 a、 e、g,而上面的代码改变了 msg.g,所以引起了变化,如果我们将函数 c 的代码改成
msg.a.b.c = 'C'; msg.e.f = 'F'; // msg.g = 'G';
这将不会引起 视图层 的变化。(因为最外层msg.g数据并没有发生变化,不会触发视图层的更新)

#triggerReactive
在 shallowReactive 中,并没有提供 trigger 方案来主动唤醒监测变化。
#总结
本质上,shallowRef 是特殊的 shallowReactive,而 ref 是特殊的 reactive。明白了这一点,理解两者的异同就会简单许多。
// ref -> reactive // ref(10) -> reactive({value:10}) // shallowRef -> shallowReactive // shallowRef(10) -> shallowReactive({value: 10}) // 所以如果是通过shallowRef创建的数据, 它监听的是.value的变化 // 因为底层本质上value才是第一层