递归监听
默认情况下,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才是第一层