vue3 响应式 API:watch()、watchEffect()

devtools/2024/11/14 3:07:16/

watch()

  • 基本概念
    • watch()用于监视响应式数据的变化,并在数据变化时执行相应的回调函数。
    • 可以监视单个响应式数据、多个响应式数据的组合,或者一个计算属性。
  • 返回值
    • 返回一个函数,调用这个函数可以停止监视。
  • 特点
    • watch() 默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。
    • watch()可以监视单个数据、多个数据的组合或计算属性。
    • 通过深度监视选项,可以方便地监视对象的内部属性变化。
    • 立即执行选项可以在特定情况下立即执行回调函数,提供更多的控制。

watch()的参数说明

watch()函数接收3个参数:数据源、回调函数、选项对象(可选)。

  • 第一个参数:数据源(被监视的数据)
    • ref()定义的数据:基本类型的响应式变量、对象类型的响应式变量。
    • reactive()定义的数据:对象类型的响应式变量。
    • 一个返回响应式数据的函数,比如一个计算属性的 getter 函数。
    • 一个包含上述内容的数组。

  • 第二个参数:回调函数
    • 当数据源发生变化时,会调用这个回调函数。
    • 回调函数接收三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数onInvalidate
      • 如果只监视一个数据源,那么新值和旧值分别对应数据源变化后的新值和变化前的旧值。
      • 如果监视多个数据源,那么新值和旧值分别是一个包含新数据源值的数组和一个包含旧数据源值的数组。
      • onInvalidate: 这是一个用于注册副作用清理的回调函数。这个回调函数可以在 watch() 的回调函数内部调用,传入一个清理函数作为参数。这个清理函数会在下次 watch() 回调执行之前被调用,以便清理上一次执行产生的副作用。
    • 示例:(newValue, oldValue) => { /* 处理变化的逻辑 */ }
      (newValue, oldValue, onInvalidate) => { /* 处理变化的逻辑, 用于注册副作用清理的回调函数 */ }

  • 第三个参数:选项对象(可选)
    • deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。参考深层侦听器。
      • 布尔值,默认值为false
      • 如果设置为true,则会进行深度监视,即当监视的是一个对象时,对象的内部属性发生变化也会触发回调函数。
    • immediate:侦听器创建时立即触发回调。第一次调用时旧值是 undefined
      • 布尔值,默认值为false
      • 如果设置为true,则在创建 watch() 时立即调用回调函数,此时旧值是 undefined,新值是当前值。
    • flush:控制回调函数的执行时机。参考回调的刷新时机及 watchEffect()。
      • 默认值是'pre',表示在 DOM 更新之前执行回调;
      • 'post'表示在 DOM 更新之后执行回调;
      • 'sync'表示同步执行回调,即立即执行回调函数,并且在响应式数据变化时同步更新视图。
    • onTrack / onTrigger:调试侦听器的依赖。参考调试侦听器。
      • onTrack(用于调试):当响应式数据被读取并作为依赖被追踪时,这个函数会被调用。可以在这个函数中记录哪些数据被读取了,以便进行调试和分析依赖关系。
      • onTrigger(用于调试):当响应式数据发生变化并触发依赖时,这个函数会被调用。可以在这个函数中记录哪些数据发生了变化,以便进行调试和分析依赖关系。
    • once:回调函数只会运行一次。侦听器将在回调函数首次运行后自动停止。
      • 布尔值,默认值为false
      • 如果设置为true,回调函数只会执行一次。侦听器将在回调函数首次运行后自动停止。

示例

监视ref()定义的【基本类型】数据

语法: watch(变量名, (newValue, oldValue) => {})
监视时直接写变量名,其本质上监视的是.value

<template><div><div>count: {{ count }}</div><button @click="addCount">点击 count+1</button></div>
</template>
<script setup lang="ts">javascript">
import { ref, watch } from 'vue';let count = ref(0)const addCount = () => {count.value++
}// watch 监视的是 ref定义的数据:count
watch(count, (newValue, oldValue) => {console.log(`count 从 ${oldValue} 变为 ${newValue}`);
})
</script>

使用watch()监视count这个响应式变量的变化。当count的值发生变化时,回调函数会被执行,打印出旧值和新值。

监视ref()定义的【对象类型】数据

语法: watch(变量名, (newValue, oldValue) => {})
监视时直接写变量名,监视的是对象的引用地址值,如果想监视对象的内部属性变化,要手动开启深度监视(deep: true

  • 如果修改的是ref()定义的对象中的属性,newValueoldValue 都是新值,因为它们是同一个对象(同一个引用地址)。
  • 如果修改整个ref()定义的对象,newValue 是新值, oldValue 是旧值,因为不是同一个对象了。

示例:

<template><div><p>姓名: {{ person.name }}</p><p>年龄: {{ person.age }}</p><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="changePerson">修改整个人</button></div>
</template>
<script setup lang="ts">javascript">
import { ref, watch } from 'vue';const person = ref({name: '张三',age: 18
});const changeName = () => {person.value.name += '哈'  
}
const changeAge = () => {person.value.age ++   
}const changePerson = () => {person.value = { name: '李四', age: 17 }
}// changeName、changeAge修改person的内部属性,不会改变person的引用地址,不会触发 watch
// changePerson 对 person 重新赋值,改变了person的引用地址,触发watch
watch(person, (newValue, oldValue) => {console.log('newValue: ',  newValue)console.log('oldValue: ', oldValue)
})
</script>

如果想要深度监视对象的内部属性变化,可以在watch()第三个参数(选项对象)中设置deep: true

javascript">watch(person, (newValue, oldValue) => {console.log('newValue: ',  newValue)console.log('oldValue: ', oldValue)
},{ deep: true })

开启深度监视(deep: true)后,修改personname属性、age属性,触发了watch()监视。
但是,watch()监视到的newValueoldValue 都是新值:
在这里插入图片描述
这是因为newValueoldValue 都是指向同一个引用地址:person的引用地址。

为了更准确地区分新旧值,可以在修改对象内部属性之前,先对旧对象进行一个副本的创建,这样在watch()回调函数中,就可以把副本作为旧值来处理。

监视reactive()定义的【对象类型】数据

监视reactive()定义的对象类型的响应式变量,watch()默认开启了深度监视,且这个深度监视是无法关闭的(设置deep: false无效)。

<template><div><p>姓名: {{ person.name }}</p><p>年龄: {{ person.age }}</p><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="changePerson">修改整个人</button></div>
</template>
<script setup lang="ts">javascript">
import { reactive, watch } from 'vue';const person = reactive({name: '张三',age: 18
});const changeName = () => {person.name += '哈'  
}
const changeAge = () => {person.age ++   
}const changePerson = () => {// Object.assign 方法会合并源对象到目标对象上。// 当对一个 reactive 对象使用 Object.assign 时,对象的引用地址不会改变。// 它实际上是在修改原始对象的属性,而不是完全替换对象。Object.assign(person,{ name: '李四', age: 17 })
}// 监视reactive()定义的对象类型的响应式变量,watch()默认开启了深度监视。
// changeName、changeAge修改person的内部属性,触发 watch
// changePerson 对 person 重新赋值,改变了person的引用地址,触发watch
watch(person, (newValue, oldValue) => {console.log('newValue: ',  newValue)console.log('oldValue: ', oldValue)
})
</script>

在这里插入图片描述
注意:新值和旧值是同一个值。因为它们都指向同一个引用地址。
changeNamechangeAgechangePerson 从本质上来讲,都是在修改person的属性,没有修改person的引用地址。

监视ref()reactive()定义的【对象类型】数据中的某个属性

  • 若该属性不是【对象类型】属性,需要写成函数形式
    • 当监视ref()reactive()定义的对象中的某个非对象类型属性时,需要写成函数形式。
    • 因为直接监视一个非对象类型的属性时,watch()无法准确追踪其变化。写成函数形式可以确保正确地获取属性值并进行监视。
  • 若该属性【对象类型】属性
    • 可以直接编写属性名进行监视
    • 可以写成函数形式
    • 如果要深度监视对象类型的属性,必须在watch()的选项中设置deep: true
<template><div><p>姓名: {{ person.name }}</p><p>年龄: {{ person.age }}</p><p>职业:{{ person.details.job }}</p><p>年级:{{ person.details.grade }}</p><button @click="changeName">修改名字</button><button @click="changeJob">修改职业</button><button @click="changeGrade">修改年级</button><button @click="changeDetails">修改详细信息</button></div>
</template>
<script setup lang="ts">javascript">
import { reactive, watch } from 'vue';const person = reactive({name: '张三',age: 18,details: {job: 'senior high school student',grade: '高三'}
});const changeName = () => {person.name += '哈'  
}const changeJob = () => {person.details.job = 'undergraduate'   
}const changeGrade = () => {person.details.grade = '大一'   
}const changeDetails = () => {person.details = {job: 'fresh graduate',grade: '毕业啦!'}
}// 监视person对象的name属性(基本类型),watch()的第一个参数写成函数式(getter函数)
// () => person.name是一个 getter 函数,它返回person对象的name属性值。
watch(() => person.name, (newName, oldName) => {console.log('newName:', newName, 'oldName:', oldName)
});// 监视details属性中的job属性,watch()的第一个参数写成函数式(getter函数)
// () => person.details.job是一个 getter 函数,它返回person.details.job的值。
watch(() => person.details.job, (newJob, oldJob) => {console.log('newJob:', newJob, 'oldJob:', oldJob)
});// 监视person对象的details属性(对象类型),watch()的第一个参数写成函数式(getter函数)
// () => person.details, 它返回person对象的details属性值。
watch(() => person.details, (newDetails, oldDetails) => {console.log('newDetails:', newDetails, 'oldDetails:', oldDetails)
});// 监视person对象的details属性(对象类型),watch()的第一个参数可以直接写变量
watch(person.details, (newDetails, oldDetails) => {console.log('newDetails:', newDetails, 'oldDetails:', oldDetails)
});// 监视person对象的details属性(对象类型),watch()的第一个参数可以直接写变量
watch(person.details, (newDetails, oldDetails) => {console.log('newDetails:', newDetails, 'oldDetails:', oldDetails)
});
</script>

在这个例子中:

  • watch()的第一个参数写成函数式(getter函数)。例如:() => person.name是一个 getter 函数,它返回person对象的name属性值。
  • changeJobchangeGrade不会触发watch(() => person.details, (newDetails, oldDetails) => {}),因为没有开启深度监视。
  • changeDetails 会触发两个监视:
    • watch(() => person.details.job, (newJob, oldJob) => {})
    • watch(() => person.details, (newDetails, oldDetails) => {})

在这里插入图片描述
如果要深度监视对象类型的属性,必须在watch()的选项中设置deep: true
例如,改变person.details.jobperson.details.grade 会 触发watch(person.details)

javascript">// 监视person对象的details属性(对象类型),写成函数式
watch(() => person.details, (newDetails, oldDetails) => {console.log('newDetails:', newDetails, 'oldDetails:', oldDetails)
}, { deep: true });

设置deep: truechangeJobchangeGradechangeDetails都会触发watch(() => person.details, (newDetails, oldDetails) => {})

监视多个响应式数据

<template><div><p>姓名: {{ name }}</p><p>年龄: {{ age }}</p><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button></div>
</template>
<script setup lang="ts">javascript">
import { ref, watch } from 'vue';let name = ref('张三')
let age = ref(18)
const changeName = () => {name.value = `${name.value}`
}
const changeAge = () => {age.value++
}watch([name, age], (newValue, oldValue) => {console.log('newValue', newValue) // newValue是一个数组:[name, age]console.log('oldValue', oldValue) // oldValue是一个数组:[name, age]
})// 当监视多个响应式数据时,回调函数接受两个数组,分别对应来源数组中的新值和旧值:
watch([name, age], ([newName, newAge], [oldName, oldAge]) => {console.log(`Name changed from ${oldName} to ${newName}`)console.log(`Age changed from ${oldAge} to ${newAge}`)
})
</script>

在这个例子中,监视了 nameage 两个响应式数据的变化。当其中任何一个数据发生变化时,回调函数会被执行,打印出两个数据的旧值和新值。

停止监视

使用 watch() 函数创建的监视器,可以通过调用watch()函数的返回值(是一个函数)来停止监视。

<template><div><div>count: {{ count }}</div><button @click="addCount">点击 count+1</button></div>
</template>
<script setup lang="ts">javascript">
import { ref, watch } from 'vue';let count = ref(0)const addCount = () => {count.value++
}// watch 监视的是 ref定义的数据:count
// watch()的返回值是一个函数,调用这个函数可以停止监视。
const stopWatcher = watch(count, (newValue, oldValue) => {console.log(`count 从 ${oldValue} 变为 ${newValue}`);// 当newValue > 10,结束监视if(newValue > 10) {// 调用stopWatcher ,结束监视stopWatcher ()}
})
</script>

立即执行回调函数

<template><div><p>Counter: {{ counter }}</p></div>
</template><script setup lang="ts">javascript">
import { ref, watch } from 'vue'const counter = ref(0)watch(counter,(newValue, oldValue) => {console.log(`Counter changed from ${oldValue} to ${newValue}`)},{ immediate: true }
)
</script>

通过设置选项对象的immediate: true,可以让watch()在创建后立即执行一次回调函数,无论被监视的数据是否已经发生变化。
此时,oldValue 的值为 undefined

只执行一次的回调函数

<template><div><p>Counter: {{ counter }}</p></div>
</template><script setup lang="ts">javascript">
import { ref, watch } from 'vue'const counter = ref(0)watch(counter,(newValue, oldValue) => {console.log(`Counter changed from ${oldValue} to ${newValue}`)},{ once: true }
)
</script>

通过设置选项对象的once: true,回调函数只会执行一次。侦听器将在回调函数首次运行后自动停止。

副作用清除

javascript">watch(id, async (newId, oldId, onCleanup) => {const { response, cancel } = doAsyncWork(newId)// 当 `id` 变化时,`cancel` 将被调用,// 取消之前的未完成的请求onCleanup(cancel)data.value = await response
})

watchEffect()

  • 基本概念
    watchEffect()立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。
    • watchEffect()自动追踪函数中使用的响应式数据,并在这些数据发生变化时重新执行函数。
    • watch()不同,watchEffect()不需要明确指定要监视的数据源,它会自动分析函数内部的依赖关系(函数中用到哪些属性,那就监视哪些属性)。
  • 返回值
    • 返回一个函数,调用这个函数可以停止副作用的执行。

watchEffect()参数说明

watchEffect()函数接收以下参数:

  • 回调函数

    • 这是一个必须的参数,它是一个函数,在这个函数内部可以访问响应式数据和执行副作用逻辑。
    • 当使用watchEffect()时,它会立即执行这个回调函数,并在其依赖的响应式数据发生变化时再次执行这个回调函数。
    • 例如:watchEffect(() => { /* 副作用逻辑 */ })

  • 选项对象(可选)

    • flush:控制回调函数的执行时机。参考回调的刷新时机及 watchEffect()。
      • 默认值是'pre',表示在 DOM 更新之前执行回调;
      • 'post'表示在 DOM 更新之后执行回调;
      • 'sync'表示同步执行回调,即立即执行回调函数,并且在响应式数据变化时同步更新视图。
    • onTrack / onTrigger:调试侦听器的依赖。参考调试侦听器。
      • onTrack(用于调试):当响应式数据被读取并作为依赖被追踪时,这个函数会被调用。可以在这个函数中记录哪些数据被读取了,以便进行调试和分析依赖关系。
      • onTrigger(用于调试):当响应式数据发生变化并触发依赖时,这个函数会被调用。可以在这个函数中记录哪些数据发生了变化,以便进行调试和分析依赖关系。

示例

基本用法

<template><div><p>a: {{ a }}</p><p>b: {{ b }}</p><p>sum: {{ sum }}</p><button @click="incrementA">点击 a+2</button><button @click="incrementB">点击 b+5</button><button @click="incrementSum">点击 sum+1</button></div>
</template><script setup lang="ts">javascript">
import { ref, watch, watchEffect } from 'vue';const a = ref(5)
const b = ref(10)
const sum = ref(0)const incrementA = () => {console.log("incrementA");a.value = a.value + 2
}const incrementB = () => {console.log("incrementB");b.value = b.value + 5
}const incrementSum = () => {console.log("incrementSum");sum.value = sum.value + 1
}// watchEffect()的返回值是一个函数,调用这个函数可以停止监视。
let stopEffect = watchEffect(() => {console.log('a:', a.value, 'b:', b.value)if(a.value < 20 && b.value < 100){sum.value = a.value + b.value}if(a.value > 20 || b.value > 100) {stopEffect()}
});watch(sum, (newSum, oldSum) => {console.log('newSum', newSum, 'oldSum', oldSum)
})
</script>

在这个例子中:

  • watchEffect()会立即执行传入的函数,并在ab的值发生变化时重新执行该函数,根据条件计算sum的值。
  • watchEffect()的返回值是一个函数,调用这个函数可以停止监视。
  • incrementSum直接修改sum的值,不会触发watchEffect()。因为sumwatchEffect()是作为计算结果被赋值的,而不是直接被读取使用。
  • watch用于监视sum的变化,当sum的值发生变化时,打印出新值和旧值。
    在这里插入图片描述

使用watchEffect并带有选项对象

javascript">watchEffect(() => {console.log('a:', a.value, 'b:', b.value)if(a.value < 20 && b.value < 100){sum.value = a.value + b.value}
},
{flush: 'post' // 在 DOM 更新之后执行回调 
}
);

在这个例子中,watchEffect()的回调函数会在ab的值发生变化时执行,并在 DOM 更新之后计算sum的值。选项对象中的flush被设置为'post',以控制回调函数的执行时机。

副作用清除

javascript">watchEffect(async (onCleanup) => {const { response, cancel } = doAsyncWork(id.value)// `cancel` 会在 `id` 更改时调用// 以便取消之前// 未完成的请求onCleanup(cancel)data.value = await response
})

watch()watchEffect()的区别

  • 数据源
    • watch()需要明确指定要监视的数据源,可以是一个响应式数据、一个返回响应式数据的函数或者一个包含多个响应式数据的数组。
    • watchEffect()不需要明确指定数据源,它会自动追踪函数内部使用的响应式数据。
  • 回调函数参数
    • watch()的回调函数接收两个参数:新值和旧值。如果监视多个数据源,新值和旧值分别是一个包含新数据源值的数组和一个包含旧数据源值的数组。
    • watchEffect()的回调函数不接收新值和旧值参数,它只接收一个用于停止副作用的清理函数作为可选参数。
  • 是否立即执行
    • watch()在创建时不会立即执行回调函数,除非设置immediate: true
    • watchEffect()在创建时会立即执行传入的函数。

watchPostEffect()​

watchEffect() 使用 flush: 'post' 选项时的别名。

watchSyncEffect()​

watchEffect() 使用 flush: 'sync' 选项时的别名。


http://www.ppmy.cn/devtools/97815.html

相关文章

Ansible可视化管理之web界面集成使用探究(未完待续)

一、前言 因某集成商管理的客户资源涉及4A接入管控要求&#xff0c;其中密码必须3个月更新一次&#xff0c;随着纳管主机的数量增多&#xff0c;手动去修改密码变得不现实&#xff0c;考虑无侵入性和资源耗用&#xff0c;便捷性等因素&#xff0c;首先选用Ansible作为此需求的…

硬件工程师必须掌握的MOS管详细知识

MOS管&#xff0c;全称为金属-氧化物半导体场效应晶体管&#xff08;Metal-Oxide-Semiconductor Field-Effect Transistor&#xff0c;MOSFET&#xff09;&#xff0c;是一种重要的半导体器件&#xff0c;广泛应用于电子工业中各种电路的开关、放大、调制、数字电路和模拟电路等…

Oracle(66)什么是虚拟列(Virtual Column)?

虚拟列&#xff08;Virtual Column&#xff09;是数据库中的一种特殊列&#xff0c;它的值并不直接存储在数据库中&#xff0c;而是根据其他列的值通过表达式计算得到的。在Oracle数据库中&#xff0c;虚拟列的值是动态计算的&#xff0c;因此不会占用额外的存储空间。这种功能…

Windows和Linux系统查看设备IP的方法

Windows 通过命令提示符 1、打开“开始”菜单&#xff0c;搜索“cmd”或“命令提示符”并打开 2、在命令提示符窗口中&#xff0c;输入ipconfig 3、查找与网络连接相关的信息&#xff0c;通常是“无线局域网适配器 WLAN”或“以太网适配器”&#xff0c;然后找到“IPv4 地址…

谷歌浏览器自动填充密码怎么设置

谷歌浏览器的自动填充密码功能为用户提供了一种安全而便捷的在线体验&#xff0c;让用户在下次登录网站的时候&#xff0c;减去重复输入密码的麻烦。下面就给大家分享一下关于谷歌浏览器自动填充密码的相关内容&#xff0c;让你更加轻松的管理自己的账户。 谷歌浏览器自动填充密…

Java面试题———MySql篇②

目录 1.事务隔离级别 2.数据库三大范式 3.索引的分类 4.索引的创建原则 5.索引失效的情况 6.如何知道索引是否失效 7.MyISAM和InnoDB的区别 1.事务隔离级别 事务隔离级别是用来解决并发事务问题的方案&#xff0c;不同的隔离级别可以解决的事务问题不一样 读未提交&…

Kubectl 常用命令汇总大全

kubectl 是 Kubernetes 自带的客户端&#xff0c;可以用它来直接操作 Kubernetes 集群。 从用户角度来说&#xff0c;kubectl 就是控制 Kubernetes 的驾驶舱&#xff0c;它允许你执行所有可能的 Kubernetes 操作&#xff1b;从技术角度来看&#xff0c;kubectl 就是 Kubernetes…

整体思想以及取模

前言&#xff1a;一开始由于失误&#xff0c;误以为分数相加取模不能&#xff0c;但是其实是可以取模的 这个题目如果按照一般方法&#xff0c;到达每个节点再进行概率统计&#xff0c;但是不知道为什么只过了百分之十五的测试集 题目地址 附上没过关的代码 #include<bits…