Vue3中 watch、watchEffect 详解

点击查看官方文档

1. watch 的使用

语法

import { watch } from "vue"
watch( name , ( curVal , preVal )=>{ //业务处理 }, options ) ;

共有三个参数,分别为:
name:需要帧听的属性;
(curVal,preVal)=>{ //业务处理 } 箭头函数,是监听到的最新值和本次修改之前的值,此处进行逻辑处理。
options :配置项,对监听器的配置,如:是否深度监听。

watch() 默认是 懒侦听的,即仅在 侦听源发生变化时才执行回调函数。

1.1 监听 ref 定义的响应式数据

1、监听 ref() 定义的【基本类型】数据:watch 参数一 直接写数据名即可,监听的是其 value值的改变。

<template>
  <div>
    <div>值:{{ count }}</div>
    <button @click="add">改变值</button>
  </div>
</template>

<script lang="ts" setup>
import { ref, watch } from 'vue';

const count = ref(0);

const add = () => {
  count.value++
};

watch(count, (newVal, oldVal) => {
  console.log('值改变了', newVal, oldVal)
})

</script>

2、监视 ref()定义的【引用类型】数据:直接写数据名,监视的是 源数据的【地址值(堆 / 栈 内存的关系)】,若想深层监视对象内部的数据,则需要 手动开启深度监视

依次点击下方三个事件的时候,会出现一个问题,那就是只有在触发 整体被修改 事件,整体更改 numdata.value 属性值的时候, watch 数据监听才会触发,而如果只更改了 numdata 数据中的 某一个属性值watch 数据监听不会触发

<template>
    <h1>{{ numdata.name }}</h1>
    <h1>{{ numdata.age }}</h1>

    <button @click="shownum">修改名</button>
    <button @click="shownum2">修改年龄</button>
    <button @click="shownum3">修改全部</button>
</template>

<script setup lang="ts" name="Box">
    import { ref, watch } from 'vue';


    type typeData = {
        name: string,
        age: number
    };

    let numdata = ref<typeData>({
        name: "张三",
        age: 1
    });

    let describe = ref<string>("");
  
    const shownum = (): void => {
        numdata.value.name = "李四";
        describe.value = "名字被修改"
    };

    const shownum2 = (): void => {
        numdata.value.age = 456;
        describe.value = "年龄被修改"

    };

    const shownum3 = (): void => {
        numdata.value = {
            name: "王五",
            age: 789
        }
        describe.value = "整体被修改"
    };

    watch(numdata, (newValue, OldValue): void => {
        console.log("数据变化了",describe.value, newValue, OldValue);
    });

</script>

针对上面的问题,我们需要在 watch 的第三个参数配置对象中 对监听的数据 手动开启深度监视{ deep: true }

数组同理:如果直接通过 数组下标索引 修改值,默认也是监听不到的,也需要 开启 { deep: true }

    watch(numdata, (newValue, OldValue): void => {
        console.log("数据变化了",describe.value, newValue, OldValue);
    }, { deep: true });

3、在上面的情况中,还有一个问题值得注意:那就是当我们,在修改数据中某一个属性的时候 会出现 newValue 和 oldValue 都是同一个新值的情况,因为它们是指向同一个源对象地址(数组同理:如果直接通过 数组下标索引 修改某个值则 newValue 和 oldValue 也会是同一个新值的情况)。

若修改了整个 ref.value的值 , newValue就则是新的值, oldValue则是旧的值,这是因为相当于从新分配了一块内存空间,新旧数据已经不指向同一块源对象地址了。

1.2监听 reactive 定义的响应式数据

1、监视 reactive() 定义的响应式数据时,且 默认自动开启了深度监视,并且该深度监视 不可通过配置项 { deep: false } 关闭

介于 reactive 的设计原理,在修改 reactive 定义的数据时,无法整体修改其变量名数据,这会使其丢失响应式,故而,在操作 reactive 定义的数据时,需保持只通过 属性 key 或者 下标索引值去修改某个数据,尽量避免直接修改 reactive 数据源本体。

<template>
  <div>
    <div>{{ obj.name }}</div>
    <div>{{ obj.age }}</div>
    <button @click="changeName">改变值</button>
    <button @click="changeAll ">修改全部值</button>
  </div>
</template>

<script lang="ts" setup>
import { reactive, watch } from 'vue';

const obj = reactive({
  name: 'zs',
  age: 14
});

const changeName = () => {
  obj.name = 'ls';
};

const changeAll = (): void => {
    console.log("修改全部属性");
    Object.assign(obj, { name: "王五", age: 789 })  //
};


watch(obj, (newVal, oldVal) =>
  {
    console.log('值改变了', newVal, oldVal)
  }
)

</script>

2、注意:当只修改嵌套的属性触发监听时 newValue 此处和 oldValue 是相等的 因为它们是同一个对象!

<template>
    <h1>{{ numdata[0] }}</h1>
    <h1>{{ numdata[1] }}</h1>

    <button @click="shownum">修改下标0</button>
    <button @click="shownum2">修改下标1</button>
</template>

<script setup lang="ts" name="Box">
import { reactive, watch } from 'vue';


let numdata = reactive<number[]>([1, 2]);

const shownum = (): void => {
    numdata[0] = 11
    console.log("下标0 被修改");
};

const shownum2 = (): void => {
    numdata[1] = 22
    console.log("下标 1 被修改");
};



watch(numdata, (newValue, OldValue): void => {
    console.log("数据变化了", newValue, OldValue);

    // 注意:当只修改嵌套的属性触发监听时 `newValue` 此处和 `oldValue` 是相等的
    // 因为它们是同一个对象!
}, { deep: false });     //   { deep: false }   默认开启深度监视,且无法通过配置项关闭。

</script>

1.3 监听多个响应式数据数据

一次性监听多个 多个数据 写法格式为一个 数组

<template>
    <h1>{{ data1 }}</h1>
    <h1>{{ data2.name }}</h1>

    <button @click="shownum">修改data1</button>
    <button @click="shownum2">修改data2</button>
</template>

<script setup lang="ts" name="Box">
    import { ref, watch, reactive } from 'vue';


    let data1 = ref<number>(1);

    let data2 = reactive({
        name: "张三"
    });

    const shownum = (): void => {
        data1.value += 1
    };

    const shownum2 = (): void => {
        data2.name = "李四"
    };


    // 同时监听 data1 和 data2.name 等多个数据的变化  写法格式为一个数组  ,回调函数接收两个数组,分别对应来源数组中的新值和旧值:
    watch([data1, () => data2.name], ([newValuedata1, newValuedata2], [OldValuedata1, OldValuedata2]): void => {
        console.log("数据变化了新值", newValuedata1, newValuedata2);
        console.log("数据变化了旧值", OldValuedata1, OldValuedata2);
    });
</script>

1.4 监听对象中某个属性的变化

能直接侦听响应式对象的属性值,需要用一个返回该属性的 getter 函数:****

当使用 getter 函数作为源时,回调只在此函数的返回值变化时才会触发。

如果你想让回调在深层级变更时也能触发,你需要使用 {deep:true } 强制侦听器进入深层级模式。在深层级模式时,如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象。

<template>
  <div>
    <div>{{ obj.name }}</div>
    <div>{{ obj.age }}</div>
    <button @click="changeName">改变值</button>
  </div>
</template>

<script lang="ts" setup>
import { reactive, watch } from 'vue';

const obj = reactive({
  name: 'zs',
  age: 14
});

const changeName = () => {
  obj.name = 'ls';
};

watch(() => obj.name, () => {
  console.log('监听的obj.name改变了')
})

</script>

1.5 默认执行(immediate)

watch 默认是懒执行的:仅当数据源变化时,才会执行回调。但在某些场景中,我们希望在创建侦听器时,立即执行一遍回调。

举例来说,我们想请求一些初始数据,然后在相关状态更改时重新请求数据。选项来强制侦听器的回调立即执行:我们可以通过传入 immediate:true

<template>
  <div>
    <div>{{obj.brand.name}}</div>
    <button @click="changeBrandName">改变值</button>
  </div>
</template>

<script lang="ts" setup>
  import { reactive, ref, watch } from 'vue';

  const obj = reactive({
    name:'zs',
    age:14,
    brand:{
      id:1,
      name:'宝马'
    }
  });

  const changeBrandName = () => {
    obj.brand.name = '奔驰1';
  };

  watch(() => obj.brand,() => {
    console.log('监听的obj.brand.name改变了')
  },{
    deep:true,
    immediate:true,
  })
</script>

2. watchEffect 的使用

watchEffect 也是一个帧听器,是一个副作用函数。 它会监听引用数据类型的所有属性,不需要具体到某个属性,一旦运行就会立即监听,组件卸载的时候会停止监听。

<template>
  <div>
    <input type="text" v-model="obj.name"> 
  </div>
</template>

<script lang="ts" setup>
  import { reactive, watchEffect } from 'vue';

  let obj = reactive({
    name:'zs'
  });
  
  watchEffect(() => {
    console.log('name:',obj.name)
  })
 
</script>

watchEffect()第一个参数是一个要运行的回调函数。这个 回调函数的参数也是一个函数,用来注册清理回调。清理回调会在该副作用下一次执行前被调用,可以用来清理无效的副作用,例如等待中的异步请求

watchEffect() 第二个参数是一个可选的配置对象,可以用来调整副作用的刷新时机或调试副作用的依赖

watchEffect((onCleanup) => {
    onCleanup(() => {
        console.log("清除副作用");
    })
}, {
    flush: 'post',  // sync  \ pre
    onTrack(e) {
    //被追踪为依赖时触发
        console.log(e);
    },
    onTrigger(e) {
    // 所追踪数据更改时触发
        console.log(e);
    }
})

3、停止侦听

当 watchEffect 在组件的 setup() 函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。

在一些情况下,也可以显式调用返回值以停止侦听:手动停止一个侦听器,请调用 watchwatchEffect返回的函数:

<template>
  <div>
    <input type="text" v-model="obj.name"> 
    <button @click="stopWatchEffect">停止监听</button>
  </div>
</template>

<script lang="ts" setup>
  import { reactive, watchEffect } from 'vue';

  let obj = reactive({
    name:'zs'
  });

  const stop = watchEffect(() => {
    console.log('name:',obj.name)
  })

  const stopWatchEffect = () => {
    console.log('停止监听')
    stop();
  }
</script>

4、刷新时机

默认先执行watch,watchEffect监听器,然后更新DOM。

nextTick去获取组件更新完成之后的DOM,在watchEffect里就不需要用nextTick()(也没法用).

watch,watchEffect如果要操作“更新之后的DOM”,就要配置 flush: 'post'。

<template>
  <div>
    <div id="value">{{count}}</div> 
    <button @click="countAdd">增加</button>
  </div>
</template>

<script lang="ts" setup>
import { ref,watch,nextTick,watchEffect} from 'vue';
 
    let count = ref(0);
    const countAdd = () => {
      count.value++;
    }

    watch(count,() =>  {
      console.log('watch',count.value)
      nextTick(() =>{
        console.log('watch',document.querySelector('#value') && document.querySelector('#value').innerText)
      });
    },
    {immediate:true })

    watchEffect(() => {
      console.log('watchEffect',count.value)
      console.log('watchEffect',document.querySelector('#value') && document.querySelector('#value').innerText)
    },
    {flush: 'post'})
</script>

posted on 2024-09-29 11:40  springsnow  阅读(365)  评论(0编辑  收藏  举报

导航