vue3中递归监听和非递归监听(系列七)

递归监听

默认情况下,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>
 

0JY0MT.png

点击 button

0JYUGq.png

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来触发
    注意点: 如果是通过shallowRef创建数据,
     那么Vue监听的是.value的变化, 并不是第一层的变化
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 ,会发现控制台提示了数据的改变,但并没有实现对界面的数据绑定。

0JYaR0.png

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才会发生变化

 

0JYdzV.png

#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 进行改变。

0JYBsU.png

原因很简单,就是我在上文提到的,shallow 会监测最外层的变化而请求更新视图层,之前在 shallowRef 中的最外层是 value ,所以我们只能改变整个 value 值来提醒变化,而这里 shallowReactive 的最外层变成了 a、 eg而上面的代码改变了 msg.g,所以引起了变化,如果我们将函数 c 的代码改成

msg.a.b.c = 'C';
msg.e.f = 'F';
// msg.g = 'G';
 

这将不会引起 视图层 的变化。(因为最外层msg.g数据并没有发生变化,不会触发视图层的更新)

0JYsZ4.png

#triggerReactive

在 shallowReactive 中,并没有提供 trigger 方案来主动唤醒监测变化。

#总结

本质上,shallowRef 是特殊的 shallowReactive,而 ref 是特殊的 reactive。明白了这一点,理解两者的异同就会简单许多。

    // ref -> reactive
    // ref(10) ->  reactive({value:10})
    // shallowRef ->  shallowReactive
    // shallowRef(10)  ->  shallowReactive({value: 10})
    // 所以如果是通过shallowRef创建的数据, 它监听的是.value的变化
    // 因为底层本质上value才是第一层
原文地址:https://www.cnblogs.com/fsg6/p/14484519.html