还在烦恼watch监听不到数据和新旧值一样咩~

309 阅读4分钟

前言:watch啊watch。。傻傻分不清楚,一会监听不到对象,一会监听到的对象新旧数据不一样,因此来总结一下😂

1. watch,watchEffect只能监听响应式对象
<template>
  <button @click="handleNum1">num1++</button>
</template>

<script setup lang="ts">
import { watch, ref} from 'vue';
/**
 * 1. watch, watchEffect只能监听响应式数据
 */
let num1 = 1;

const handleNum1 = () => {
    num1++
}

// 会报错
watch(num1, (v, preV) => { 
    console.log('普通数据监听')
    console.log('num1', v, preV)
})

小结:watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组

2. watch监听ref对象
<template>
  <button @click="handleNum1">基本类型 num1++</button>
  <button @click="handleNum2">对象类型 num2++</button>
</template>

<script setup lang="ts">
import { watch, ref } from "vue";
let num1 = ref(1);
let num2 = ref({
  name: "lucy",
  age: 22,
});

const handleNum1 = () => {
  num1.value++;
};

const handleNum2 = () => {
  num2.value.age = num2.value.age + 1;
};

// 监听ref基本类型
watch(num1, (v, preV) => {
  console.log("数据监听");
  console.log("num1", v, preV);
});

// 监听ref对象整体
watch(
  num2,
  (v, preV) => {
    console.log("监听ref对象整体");
    console.log("num2整体", v, preV);
  },
  {
    deep: true,
  }
);

// 监听ref对象某个属性
watch(
  ()=>num2.value.age,
  (v, preV) => {
    console.log("监听ref对象某个属性");
    console.log("num2.value.age", v, preV);
  },
);
</script>

小结:

  1. 监听ref基本类型:直接监听ref本身即可,新旧值不同
  2. 监听ref对象整体:需要设置deep:true(对象为深度嵌套也能监听到),才能监听到变化,但是新旧值一样(后续处理)
  3. 监听ref对象中的某个属性,watch第一个参数为该属性的getter函数
3. watch监听ref对象--handle新旧值一样

image.png

<template>
  <button @click="handleNum2">对象类型 num2++</button>
</template>

<script setup lang="ts">
import { watch, ref, computed } from "vue";
let num2 = ref({
  name: "lucy",
  age: 22,
  friends: {
    lucky: 21,
    tom: 18,
  },
});

// 使用computed对num2进行保存
const data = computed(() => {
  return JSON.parse(JSON.stringify(num2.value));
});

const handleNum2 = () => {
  num2.value.age = num2.value.age + 1;
};

// 监听ref对象整体
watch(
  () => data.value,
  (v, preV) => {
    console.log("监听ref对象整体");
    console.log("num2", v, preV);
    console.log("num2整体", JSON.stringify(v) === JSON.stringify(preV)); //false
  }
);
</script>

小结:

  1. watch监听对象整体时,新旧值是一样的,因为新旧值的引用地址指向同一个对象,所以需要用JSON.parse(JSON.stringify())对ref指定的对象进行处理,并用computed使其为响应式对象,并且收集依赖,当数据改变时,可以获取到最新的值,所以必须用computed
4. watch监听ref数组
<template>
  <button @click="handleNum2">对象类型 num2++</button>
</template>

<script setup lang="ts">
import { watch, ref, computed } from "vue";
let num2 = ref<Array<number>>([]);

const handleNum2 = () => {
  num2.value.push(1)
};

// 监听ref对象整体
watch(
  num2,
  (v, preV) => {
    console.log("监听ref对象整体");
    console.log("num2", v, preV);
    console.log("num2整体", JSON.stringify(v) === JSON.stringify(preV)); //false
  },
  {
    deep:true
  }
);
</script>

image.png 同样可以监听到变化,但是新旧值一样,想解决这个问题,将num2替换为[...num2]就可以了,但是如果数组里是对象,这种展开的方式,新旧值依旧是一样的~,还没有找到很好的解决方式

5. watch取消监听
<template>
  <button @click="handleNum2">对象类型 num2++</button>
  <button @click="cancelWatch">停止监听</button>
</template>

<script setup lang="ts">
import { watch, ref, computed } from "vue";
let num2 = ref<Array<{ age: number }>>([{ age: 18 }]);

const handleNum2 = () => {
  num2.value[0].age = num2.value[0].age + 1;
};

const data = computed(() => {
    return [...num2.value]
})

const cancelWatch = () => {
    stopWatch()
}

// 监听ref对象整体
const stopWatch = watch(
  () => data.value,
  (v, preV) => {
    console.log("监听ref对象整体");
    console.log("num2", v, preV);
    console.log("num2整体", JSON.stringify(v) === JSON.stringify(preV)); //false
  },
  {
    deep: true,
  }
);
</script>

vue3的watch终止监听,只需要将watch赋值给一个变量,当达到条件调用watch赋值的那个变量就可以终止监听了

总结:上面的watch监听,只探讨了ref的情况,但是实际开发中,通常普通数据用ref,对象用reactive,ref参数是对象类型时,其实底层的本质还是reactive,所以这里就不再讨论了

补充:

时隔1年多,又再次遇到了这个问题,查看了文档(cn.vuejs.org/guide/essen… ,有了新的总结

  1. watch监听可以监听ref对象,或者是一个getter方法(箭头函数 () => x.value + y.value
  2. watch监听reactive对象时,不能监听a.num此时得到的是一个具体的值,需要监听()=>a.num
  3. watch监听多个对象时,用数组
  4. watch监听reacive对象的时候,其实会隐式创建一个深层侦听器,效果等同于加上{deep:true},另外ref的值不是基本类型,为对象的时候,也是如此,会隐式创建一个深层侦听器,如以下示例
<template>
    <div>watch和watchEffect</div>
    <p>watch: {{ num2.num }}</p>
    <button @click="updateWatchValue">修改watch监听的值</button>
</template>
<script setup>
import {watch, watchEffect, ref, reactive} from 'vue';
const num2 = ref({
    num: 1
})

const updateWatchValue = () => {
    num2.value.num ++
}

// 如果监听的是num2,会监听不到,因为只有num2.value才是一个Proxy对象
watch(num2.value, (newVal, oldVal) => {
        console.log('watch监听', newVal, oldVal)
    },
)
</script>

image.png