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() 函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。
在一些情况下,也可以显式调用返回值以停止侦听:要 手动停止一个侦听器
,请调用 watch
或 watchEffect
返回的函数:
<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 阅读(1224) 评论(0) 编辑 收藏 举报