017_watch监视_情况一
watch他本身的含义就是监视, 监视数据的变化,为什么要使用watch?大家想一想,有没有这种场景,age一旦大于18,我们要做点什么,又不如order 订单 一旦金额大于1万元,我就要联系后端请求优惠卷等等这些数据去走逻辑,挺常用的,在vue3官方文档里面,明确表达了watch能监视4种数据。
1, ref 定义的数据
2, reactive 定义的数据
3,函数返回一个值(getter函数)
4, 一个包含上述内容的数组。
笔记

3.9 [watch]
1,作用: 监视数据的变化(和Vue2中的watch作用一致)
2,特点: Vue3中的watch只能监视以下四种数据:
1) ref 定义的数据。
2) reactive 定义的数据
3) 函数返回一个值。
4) 一个包含上述内容的数组。
我们在 Vue3 中使用 watch 的时候,通常会遇到以下几种情况:
情况一:
监视 ref 定义的 [基本类型] 数据: 直接写数据名即可,监视的是其value值的改变。
<template>
<div class="person">
<h2>
watch情况一: 监视[ref]定义的[基本类型]数据,默认监视的就是value的值 </h2>
<h3>当前求和:{{sum}}</h3>
<button @click="changeSum">sum+1</button>
</div>
</template>
准备数据


vue2在watch的写法,要监视的逻辑统统写入watch里面,如下图所示



watch的写法 watch(监视谁,回调函数)

我们这里监视sum,回调函数我们就使用箭头函数 即 watch(sum, () => {})

sum变化了,我们要传入两个参数 变化前后两个值 (newValue,oldValue)

vue3里面我们绝对不会用this

sum变化了,他会收到两个值(新的值,旧的值),然后我们打印一下这两个值


页面上值与打印的最新值是保持同步的

如果你持续点下去,结果如下图所示


以上这就watch去监视ref定义的基本类型

watch只能监视ref定义的数据
如果监视的数据为 sum.value 编辑器飘红报错



谁才是ref定义的数据 -> sum 所以sum.value会报错 如下图所示

两个插件 TypeScript Vue Plugin(Volar)和Vue Language Features(Volar)


如何解除监视?当 sum 的值大于等于 10 的时候,要解除监视,如图所示

当调用watch()的时候,会返回一个值x,打印x的值,watch()返回一个回调函数,具体如下
() => {
effect2.stop();
if (scope && scope.active) {
remove(scope.effects, effect2);
}
}
----
老师的
() => {
effect2.stop();
if (instance && instance.scope) {
remove(instance.scope.effects, effect2);
}
}



当 sum 大于等于 10 立即结束监视


页面已经25了,监视从10就停下来了

你如果不写,他就会持续的监视

watch监视_情况一 实现代码如下:
<template>
<div class="person">
<h1>情况一: 监视【ref】定义的【基本类型】数据</h1>
<h2>当前求和为: {{ sum }} </h2>
<button @click="changeSum">点我sum+1</button>
</div>
</template>
<script setup lang="ts" name="Person">
import { ref,watch } from 'vue'
// 定义一个响应式数据
let sum = ref(0)
// 定义一个方法
function changeSum() {
// sum.value++
sum.value += 1
}
// 监视 情况一: 监视【ref】定义的【基本类型】数据
// sum的变化 sum.value报错,因为sum.value是基本类型数据,监视的是它的值的变化,而不是引用类型的地址变化。
const stopWatch = watch(sum, (newValue, oldValue) => {
console.log('sum变化了', newValue, oldValue);
// 停止监视
if (newValue >= 10) {
stopWatch()
}
})
</script>
<style scoped>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button {
margin: 0 5px;
}
li {
font-size: 20px;
}
</style>

018_watch监视_情况二
情况二
监视 ref 定义的[对象类型]数据: 直接写数据名,监视的是对象的[地址值],若想监视对象内部的数据,要手动开启深度监视。
注意:
1,若修改的是ref定义的对象中的属性,newValue和oldValue都是新值,因为他们是同一个对象
2,若修改整个 ref 定义的对象, newValue 是新值,oldValue 是旧值,因为不是同一个对象了

笔记




修改整个人


红色指向是修改对象里面的属性,没有修改对象,地址值是不会发生变化

蓝色指向修改的对象,地址值是会发生改变的

watch()监视的是谁


watch()根本就不关心name,age等属性,watch()只关心的是整个地址值是否发生变化

我们点击'修改整个人',返回如下页面



有情况出现,开发时我们就需要监视name,age等对象属性的变化,该如何处理

要开启深度监视,需要传递三个参数:
第1个参数: person 绿色指向
第2个参数:回调函数 红色指向
第3个参数: {deep:true} watch()的配置对象
---
完整示例代码
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue);
}, {deep: true})
修改名字可以监视

修改年龄可以监视

修改整个人也可以监视


第3个参数 watch()的配置对象 红色指向


还可以配置watch()监视选项{immediate:true},如果有此项配置,一上来,不论数据是否发生改变,他都要先执行 选项{immediate:true},告诉你新的值是多少。新的值就是当前的值, 旧的值是undifined








点击 '修改整个人',张三就是旧的值,李四此时是新的值了,如下图所示


绿色的person发生改变时,红色函数在你所监视的地址值发生改变时调用里面的回调函数。


绿色person发生改变,他才会去调用红色函数 如下图所示


有些时候新的值和旧的值是一样的





总结


watch的第一个参数是: 被监视的数据
watch的第二个参数是: 监视的回调(箭头函数)
watch的第三个参数是: 配置对象(deep,immediate,flush等等)
{deep:true,immediate:true,flush:'post'}


watch监视_情况二 代码实现如下:
1, src/components/Person.vue
<template>
<div class="person">
<h1>情况二: 监视【ref】定义的【对象类型】数据</h1>
<h2>姓名: {{ person.name }}</h2>
<h2>年龄: {{ person.age }}</h2>
<h2>爱好: {{ person.hobbies }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeHobbies">修改爱好</button>
<button @click="changePerson">修改整个人</button>
</div>
</template>
<script setup lang="ts" name="Person">
import { ref,watch } from 'vue'
// 数据
let person = ref({
name: '张三',
age: 18,
hobbies: ['抽烟','喝酒','烫头']
})
// 方法
function changeName() {
person.value.name += '~'
}
function changeAge() {
person.value.age += 1
}
function changeHobbies() {
person.value.hobbies[0] = '打游戏'
}
function changePerson() {
person.value = {
name: '李四',
age: 20,
hobbies: ['写代码','唱歌','旅游']
}
}
// 监视
// 情况二: 监视【ref】定义的【对象类型】数据
// 监视对象类型的数据,默认情况下监视的是对象的引用地址,
// watch(person,(newValue,oldValue) => {
// console.log('person变化了',newValue,oldValue)
// })
// 监视对象类型的数据
// 监视的是对象的地址值的变化情况
// 如果要监视对象内部属性的变化,则需要手动开启深度监视
// watch(person,(newValue,oldValue)=>{
// console.log('person变化了',newValue,oldValue);
// }, {deep: true, immediate: true})
// 监视 新的值和旧的值
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue);
}, {deep: true})
</script>
<style scoped>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button {
margin: 0 5px;
}
li {
font-size: 20px;
}
</style>

019_watch监视_情况三
笔记
监视 reactive 定义的[对象类型]数据,且默认开启了深度监视。



不论是ref还是reactive绿色框里面的不需要更改


能修改整个person吗?不能

reactive定义的数据有一个局限性,不可整体修改

我们需要用 Object.assign() 这个API接口实现整体替换

function changePerson() {
Object.assign(person,{name:'李四',age:80})
}
ref定义的人对象与reactive定义的人对象是有本质区别的
ref定义的人是真正替换了整个人,已经是一个新的对象了
reactive定义的人是批量的修改了人里面的属性而与,所以对象还是原来的对象



reactive定义的人对象的数据是不能整个改变的

person 地址值没有发生变化,

还是那个person,只不过我们要将后面的值分配进来,对象还是那个对象,只不过


对象还是那个对象,只不过将新的绿色的数据塞进红色person对象里面去了,将原来的name与age替换了

ref定义的人对象数据是可以整个改变的


只能这么做,vue3就这么设计的,使用Object.assign()接口去实现批量替换对象中的属性。注意:对象还是原来的对象


监视


1,2,3都能监视到



person是一个reactive对象

验证 reactive 是默认开启深度监视的



reactive 隐式创建了深度监视,是非常好的

reactive创建的深度监视是你无法关闭的,页面仍然可以获取数据


newValue,oldValue值没有改变,因为我们一直在原对象上操作属性,压根就没有新对象所有 newValue,oldValue 一样,地址值没有改变


020_watch监视_情况四
笔记
情况四
情况四
监视ref或reactive定义的[对象类型]数据中的某个属性,注意点如下:
1, 若该属性值不是[对象类型],需要写成函数形式.
2, 若该属性值是依然是[对象类型],可直接编,也可写成函数,不过建议写成函数。
总结:
监视的要是对象里的属性,那么最好写函数式,注意:若是对象监视的是地址值,需要关注对象内部需要手动开启深度监视.




脚本和结构都删除

先监视对象里面的基本属性

如何来监视某个属性
下图表示监视的person.name不是规定的四种数据源,所以报错




若只要监视name属性,先正常写法person.name,但是你要加工一下成getter函数
加工成getter函数有如下两种写法 ()=>person.name ()=>{return person.name}
1,简写
// 监视
watch(()=>person.name, (newValue,oldValue) => {
console.log('person变了', newValue,oldValue)
})
2,完整写法
watch(()=>{return person.name}, (newValue,oldValue) => {
console.log('person变了', newValue,oldValue)
})
以上两种写法返回如下图示 都一样

解释:getter函数,就能返回一个值的函数 https://cn.vuejs.org/


监视name 新值和旧值不一样 终于实现只监视对象person中的某一个属性
第一个参数简写: () => person.name

第一个参数完整写法: () => {return person.name}



watch()
侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。
详细信息
watch() 默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。
第一个参数是侦听器的源。这个来源可以是以下几种:
-
一个函数,返回一个值
-
一个 ref
-
一个响应式对象
-
...或是由以上类型的值组成的数组
第二个参数是在发生变化时要调用的回调函数。这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。
当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。
第三个可选的参数是一个对象,支持以下这些选项:
-
immediate:在侦听器创建时立即触发回调。第一次调用时旧值是undefined。 -
deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。在 3.5+ 中,此参数还可以是指示最大遍历深度的数字。参考深层侦听器。 -
flush:调整回调函数的刷新时机。参考回调的刷新时机及 watchEffect()。 -
onTrack / onTrigger:调试侦听器的依赖。参考调试侦听器。 -
once:(3.4+) 回调函数只会运行一次。侦听器将在回调函数首次运行后自动停止。
与 watchEffect() 相比,watch() 使我们可以:
-
懒执行副作用;
-
更加明确是应该由哪个状态触发侦听器重新执行;
-
可以访问所侦听状态的前一个值和当前值。
侦听数据源类型
watch 的第一个参数可以是不同形式的"数据源":它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组:
const x = ref(0)
const y = ref(0)
// 单个 ref
watch(x, (newX) => {
console.log(`x is ${newX}`)
})
// getter 函数
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
这是定义的reactive响应式数据和监视reactive对象中某一个属性的代码如下图所示


name属性值不是【对象类型】,需要写成函数形式 如下图所示


接下来我们监视对象类型

监视的person.car里面的汽车,因为汽车是对象,所以可以直接写



watch第一个参数直接写 person.car 不报错,但是监视不断整个car的情况了,如图所示


所以我们要把person.car写成 () => person.car 函数形式,就可以监视到汽车的整个变化情况了,这里跟写不写第三个参数选项{deep:true}没有关系,但是如果要监视基本类型属性,必须开启深度监视选项{deep:true}


如果将选项 {deep:true}去掉,第一台与第二台就不能监视了,只有开启深度监视,他们才你实现监视,但是他们的新值和旧值是一样的。


总结:
由于包了函数此时监视的是地址值,而此时基本类型的数据就无法监视了



当我们加上选项{deep:true}基本类型和地址值都可以监视了



小结:
你只要监视响应式对象中的某个属性,不论是基本类型,还是对象类型,我们直接写函数就可以解决所有问题,但是你要知道基本类型走的是地址值,在地址值监视,你只要开一个深度监视{deep:true}就解决问题,这就是最佳实践.



watch监视_情况四 实现代码如下
1, src/components/Person.vue
<template>
<div class="person">
<h2>姓名: {{ person.name }}</h2>
<h2>年龄: {{ person.age }}</h2>
<h2>汽车: {{ person.car.c1 }},{{ person.car.c2 }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeFirstCar">修改第一台车</button>
<button @click="changeSecondCar">修改第二台车</button>
<button @click="changeCar">修改整个汽车</button>
</div>
</template>
<script setup lang="ts" name="Person">
import { reactive, watch } from 'vue'
// 数据
let person = reactive({
name: '张三',
age: 18,
car: {
c1: '宝马',
c2: '奔驰'
}
})
// 方法
function changeName() {
person.name += '~'
}
function changeAge() {
person.age += 1
}
function changeFirstCar() {
person.car.c1 = '奥迪'
}
function changeSecondCar() {
person.car.c2 = '特斯拉'
}
function changeCar() {
person.car = {
c1: '比亚迪',
c2: '五菱宏光'
}
}
// 监视 情况四: 监视响应式对象中的某个属性,且属性是基本类型的,要写成函数的形式,监视多层嵌套的属性值时,要写成函数的形式
// watch(()=>person.name, (newValue,oldValue) => {
// console.log('person.name变了', newValue,oldValue)
// })
// watch(()=>person.age, (newValue,oldValue) => {
// console.log('person.age变了', newValue,oldValue)
// })
// watch(()=>{return person.name}, (newValue,oldValue) => {
// console.log('person变了', newValue,oldValue)
// })
// 监视 情况四: 监视响应式对象中的某个属性,且属性是对象类型的,可以直接写,也可以写成函数的形式,推荐写函数
watch(()=>person.car, (newValue,oldValue) => {
console.log('person.car变了', newValue,oldValue)
},{deep:true})
</script>
<style scoped>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button {
margin: 0 5px;
}
li {
font-size: 20px;
}
</style>

021_watch监视_情况五
监视上述多个数据
笔记

开发里面监视多个使用频率还是很多的




newValue是整个红色数组




watch监视情况五 实现代码如下
1, src/components/Person.vue
<template>
<div class="person">
<h1>情况五: 监视上述多个数据</h1>
<h2>姓名: {{ person.name }}</h2>
<h2>年龄: {{ person.age }}</h2>
<h2>汽车: {{ person.car.c1 }},{{ person.car.c2 }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeFirstCar">修改第一台车</button>
<button @click="changeSecondCar">修改第二台车</button>
<button @click="changeCar">修改整个汽车</button>
</div>
</template>
<script setup lang="ts" name="Person">
import { reactive, watch } from 'vue'
// 数据
let person = reactive({
name: '张三',
age: 18,
car: {
c1: '宝马',
c2: '奔驰'
}
})
// 方法
function changeName() {
person.name += '~'
}
function changeAge() {
person.age += 1
}
function changeFirstCar() {
person.car.c1 = '奥迪'
}
function changeSecondCar() {
person.car.c2 = '特斯拉'
}
function changeCar() {
person.car = {
c1: '比亚迪',
c2: '五菱宏光'
}
}
// 监视 情况五: 监视上述的多个数据
// watch([()=>person.name,()=>person.car.c1], (newValue,oldValue) => {
// console.log('person.car变了', newValue,oldValue)
// },{deep:true})
watch([()=>person.name,person.car], (newValue,oldValue) => {
console.log('person.car变了', newValue,oldValue)
},{deep:true})
</script>
<style scoped>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button {
margin: 0 5px;
}
li {
font-size: 20px;
}
</style>
022_watchEffect
3.10 【watchEffect】
官网: 立即运行一个函数,同时响应式第追踪其依赖,并在依赖更改时重新执行该函数.
watch 对比 watchEffect
1, 都能监听响应式数据的变化,不同的是监听数据变化的方式不同
2, watch: 要明确指出监视的数据
3, watchEffect: 不用明确指出监视的数据(函数中用到哪些属性,那就监视哪些属性)。

笔记

watchEffect()
立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。
类型
function watchEffect(
effect: (onCleanup: OnCleanup) => void,
options?: WatchEffectOptions
): WatchHandle
type OnCleanup = (cleanupFn: () => void) => void
interface WatchEffectOptions {
flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
}
interface WatchHandle {
(): void // 可调用,与 `stop` 相同
pause: () => void
resume: () => void
stop: () => void
}
详细信息
第一个参数就是要运行的副作用函数。这个副作用函数的参数也是一个函数,用来注册清理回调。清理回调会在该副作用下一次执行前被调用,可以用来清理无效的副作用,例如等待中的异步请求 (参见下面的示例)。
第二个参数是一个可选的选项,可以用来调整副作用的刷新时机或调试副作用的依赖。
默认情况下,侦听器将在组件渲染之前执行。设置 flush: 'post' 将会使侦听器延迟到组件渲染之后再执行。详见回调的触发时机。在某些特殊情况下 (例如要使缓存失效),可能有必要在响应式依赖发生改变时立即触发侦听器。这可以通过设置 flush: 'sync' 来实现。然而,该设置应谨慎使用,因为如果有多个属性同时更新,这将导致一些性能和数据一致性的问题。
返回值是一个用来停止该副作用的函数。
示例
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> 输出 0
count.value++
// -> 输出 1
停止侦听器:
const stop = watchEffect(() => {})
// 当不再需要此侦听器时:
stop()
暂停/恢复侦听器:
const { stop, pause, resume } = watchEffect(() => {})
// 暂停侦听器
pause()
// 稍后恢复
resume()
// 停止
stop()
副作用清理:
watchEffect(async (onCleanup) => {
const { response, cancel } = doAsyncWork(newId)
// 如果 `id` 变化,则调用 `cancel`,
// 如果之前的请求未完成,则取消该请求
onCleanup(cancel)
data.value = await response
})
3.5+ 中的副作用清理:
import { onWatcherCleanup } from 'vue'
watchEffect(async () => {
const { response, cancel } = doAsyncWork(newId)
// 如果 `id` 变化,则调用 `cancel`,
// 如果之前的请求未完成,则取消该请求
onWatcherCleanup(cancel)
data.value = await response
})
选项:
watchEffect(() => {}, {
flush: 'post',
onTrack(e) {
debugger
},
onTrigger(e) {
debugger
}
})
watchPostEffect()
watchEffect() 使用 flush: 'post' 选项时的别名。
watchSyncEffect()
watchEffect() 使用 flush: 'sync' 选项时的别名。
删除所有结构和脚本 如图所示

需要监视两个数据水温和水位



只关注最新值


拿到最新的水温(newTemp)和最新的水位(newHeight)



判断条件 满足就发送请求




源码和实现效果




逻辑里面监视了两个值

如果逻辑里面要监视7个值,我们是不是要写7个监视值,是不是很麻烦


watch() 与 watchEffect() 区别

watch() 必须明确指出要监视是数据

watchEffect()他神奇在哪里?你不用告诉他监视谁,你直接用就可以了,他会自己去分析监视谁。

如果有多个数据需要监视,我们可以用watchEffect()解决问题
绿色的回调函数监视谁?

验证 有点像watch()里面加了{immediate: true}感觉,立即监视



写监视代码




他只会监视两个,剩余的8g个他不会重新调用


watchEffect() 是全自动的监视


使用 watchEffect去写watch能省很多力气

官网依据


_watchEffect 实现代码如下
1, src/components/Person.vue
<template>
<div class="person">
<h2>需求:当水温达到60度或水位80cm时,给服务器发送请求</h2>
<h2>当前水温为: {{ temp }}℃</h2>
<h2>当前水位为: {{ height }}cm</h2>
<button @click="changeTemp">水温+10</button>
<button @click="changeHeight">水位+10</button>
</div>
</template>
<script setup lang="ts" name="Person">
import { ref,watch,watchEffect} from 'vue'
// 数据
let temp = ref(10)
let height = ref(0)
// 方法
function changeTemp() {
temp.value += 10
}
function changeHeight() {
height.value += 10
}
// 监视--watch()
// watch([temp, height], ([newTemp, newHeight]) => {
// if (newTemp >= 60 || newHeight >= 80) {
// console.log('发送请求');
// }
// })
// // 需求根据水温和水位发送请求
// watch([temp,height],(value)=>{
// // 从value中获取最新的水温(newTemp)和水位(newHeight)数据
// let [newTemp,newHeight] = value;
// // console.log(newTemp, newHeight);
// // 逻辑 判断是否满足条件,如果满足则发送请求
// if(newTemp >= 60 || newHeight >= 80) {
// console.log('给服务器发送请求');
// }
// })
// 监视--watchEffect()
// watchEffect(()=>{
// if(temp.value >= 60 || height.value >= 80) {
// console.log('给服务器发送请求');
// }
// })
watchEffect(()=>{
// console.log('@')
if (temp.value >= 60 || height.value >= 80) {
// 逻辑判断是否满足条件,如果满足则发送请求
console.log('给服务器发送请求');
}
})
</script>
<style scoped>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button {
margin: 0 5px;
}
li {
font-size: 20px;
}
</style>