watch
和watchEffect
都是vue3
中的监听器,但是在写法和使用上是有区别的,主要是介绍一下watch
和watchEffect
的使用方法以及他们之间的区别。
watch 的工作原理:侦听特定的数据源,并在回调函数中执行副作用。它默认是惰性的——只有当被侦听的源发生变化时才执行回调,不过,可以通过配置 immediate 为 true 来指定初始时立即执行第一次。可以通过配置 deep 为 true,来指定深度监视
immdiate: 默认情况下,侦听器需要 data 后面值改变了才会生效,若需要侦听器一进入页面就生效,那就需要使用 immediate
deep: 默认情况下,侦听器只会监听数据本身的改变,若要进行深度监听,那就需要使用 deep。 immediate 和 deep 配置在第三个参数对象里
第一个参数:监听谁,第二个参数:回调函数,第三个参数:配置对象
watch监听单个数据
1. <template> 2. <input type="text" v-model="text1" /> 3. </template> 4. 5. <script setup> 6. import { ref, watch } from 'vue' 7. const text1 = ref('') 8. 9. watch(text1, (newVal, oldVal) => {console.log('监听单个数据', newVal, oldVal) 10. }) 11. </script>
监听多个数据(初始值为空,并没有进行打印)
<template> <input type="text" v-model="text1" placeholder="text1值" /> <hr /> <input type="text" v-model="text2" placeholder="text2值" /> </template> <script setup> import { ref, watch, reactive } from 'vue' const text1 = ref('') const text2 = ref('') watch([text1,text2], (newValue, oldValue) => { console.log('监听一组数据变化', newValue, oldValue) }) // { immediate: true } </script>
监听一个对象--问题
<template> name: <input type="text" v-model="student.name" /> <hr/> age: <input type="number" v-model="student.age" /> </template> <script setup> import { reactive, watch } from 'vue' const student = reactive({name: '',age: '' }) watch(student, (newVal, oldVal) => { console.log('newVal', newVal) console.log('oldVal', newVal) }) </script>
监听对象的某一个值
1. <template> 2. name: <input type="text" v-model="student.name" /> 3. <hr /> 4. age: <input type="number" v-model="student.age" /> 5. </template> 6. <script lang="ts" setup> 7. import { reactive, watch } from 'vue' 8. const student = reactive({ 9. name: '', age: '' 10. }) 11. watch(() => student.name, (newVal, oldVal) => { 12. console.log('newVal', newVal) 13. console.log('oldVal', newVal) 14. }, { 15. deep: true, immediate: true 16. }) 17. 18. </script>
遇到的坑:
1.监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)
<template> <h2>姓名:{{ person.name }}</h2> <h2>年龄:{{ person.age }}</h2> <h2>薪资:{{ person.job.j1.salary }}K</h2> <button @click="person.name += '~'">修改姓名</button> <button @click="person.age++">增长年龄</button> <button @click="person.job.j1.salary++">涨薪</button> <hr /> <input type="text" v-model="text1" /> </template> <script> import { reactive, watch, ref } from 'vue' export default { setup() { //数据 let person = reactive({ name: '张三', age: 18, job: { j1: { salary: 20 } } }) const text1 = ref('') /* 情况三:监视reactive所定义的一个响应式数据的全部属性 1.注意:此处无法正确的获取oldValue 2.注意:强制开启了深度监视(deep配置无效)-不管嵌套有多深 */ watch(person, (newValue, oldValue) => { console.log('person变化了', newValue, oldValue) }, { deep: false }) //此处的deep配置无效 */ watch(text1, (newVal, oldVal) => { console.log('监听单个数据', newVal, oldVal) }) //返回一个对象(常用) return { person, text1 } } } </script>
2.监视reactive定义的响应式数据中某个属性时:deep配置有效
<template> <h2>姓名:{{ person.name }}</h2> <h2>年龄:{{ person.age }}</h2> <h2>薪资:{{ person.job.j1.salary }}K</h2> <button @click="person.name += '~'">修改姓名</button> <button @click="person.age++">增长年龄</button> <button @click="person.job.j1.salary++">涨薪</button> <hr /> <input type="text" v-model="text1" /> </template> <script> import { reactive, watch, ref } from 'vue' export default { setup() { //数据 let person = reactive({ name: '张三', age: 18, job: { j1: { salary: 20 } } }) const text1 = ref('') /* 情况三:监视reactive所定义的一个响应式数据的全部属性 1.注意:此处无法正确的获取oldValue 2.注意:强制开启了深度监视(deep配置无效)-不管嵌套有多深 */ watch(person, (newValue, oldValue) => { console.log('person变化了', newValue, oldValue) }, { deep: false }) //此处的deep配置无效 */ watch(text1, (newVal, oldVal) => { console.log('监听单个数据', newVal, oldVal) }) watch(() => person.age, (newValue, oldValue) => { console.log('person的age变化了', newValue, oldValue) }, { deep: true }) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效 //返回一个对象(常用) return { person, text1 } } } </script>
3.使用ref定义数组时:(触发changeArr的时候,监听不到)
<template> <button @click="changeArr">按钮</button> </template> <script> import { reactive, watch, ref } from 'vue' export default { setup() { const array = ref([1, 2, 3]); const changeArr = () => { console.log('触发'); array.value = []; } watch(array.value, (now, old) => { console.log(now, old); // 触发changeArr的时候,监听不到 }) return { array, changeArr } } } </script>
解决方案:
<template> <button @click="changeArr">按钮</button> </template> <script> import { reactive, watch, ref } from 'vue' export default { setup() { const array = ref([1, 2, 3]); const changeArr = () => { console.log('触发'); array.value = []; } // watch(array.value, (now, old) => { // console.log(now, old); // 触发changeArr的时候,监听不到 // }) watch(() => [array.value], (now, old) => { console.log(now, old) }) return { array, changeArr } } } </script>
watchEffect
watchEffect 函数的特点:
- 优点:
- 会自动收集依赖,不需要手动传递侦听内容——自动侦听回调函数中使用到的响应式数据。
- 默认 immdiate 是 true,所以初始化时会立即执行。
- 缺点:
- 无法获得变化前的值(oldVal)。
watch()
是懒执行的:当数据源发生变化时,才会执行回调。但在某些场景中,我们希望在创建侦听器时,立即执行一遍回调。
watchEffect
相当于将watch
的依赖源和回调函数合并,当任何你有用到的响应式依赖更新时,该回调函数便会重新执行。不同于 watch
,watchEffect
的回调函数会被立即执行(即 { immediate: true }
)
简单来说,watchEffect 是 Vue3 中的一个响应式 API,它允许你监听响应式状态的变化,并在其发生变化时触发副作用函数。这个特性非常有用,在我们需要对响应式数据进行操作的时候,我们可以在监听到变化后马上做出反应。
<template> name: <input type="text" v-model="student.name" /> age: <input type="number" v-model="student.age" /> </template> <script setup> import { reactive, watchEffect } from 'vue' const student = reactive({name: '',age: '' }) watchEffect(() => {console.log('name: ',student.name, 'age: ', student.age) }) </script>
watcheffect停止监听
<template> <div> <input type="text" v-model="obj.name"> <button @click="stopWatchEffect">停止监听</button> </div> </template> <script> import { reactive, watchEffect } from 'vue'; export default { setup() { let obj = reactive({ name: 'zs' }); const stop = watchEffect(() => { console.log('name:', obj.name) }) const stopWatchEffect = () => { console.log('停止监听') stop(); } return { obj, stopWatchEffect, } } } </script>
watchEffect的副作用
什么是副作用(side effect
),简单的说副作用就是执行某种操作,如对外部可变数据或变量的修改,外部接口的调用等。watchEffect
的回调函数就是一个副作用函数,因为我们使用watchEffect
就是侦听到依赖的变化后执行某些操作。
Vue3
的watchEffect
侦听副作用传入的函数可以接收一个 onInvalidate
函数作为入参,用来注册清理失效时的回调
当以下情况发生时,这个失效回调会被触发:
- 副作用即将重新执行时(即依赖的值改变)
- 侦听器被停止 (通过显示调用返回值停止侦听,或组件被卸载时隐式调用了停止侦听)
import { watchEffect, ref } from 'vue' const count = ref(0) watchEffect((onInvalidate) => { console.log(count.value) onInvalidate(() => { console.log('执行了onInvalidate') }) }) setTimeout(()=> { count.value++ }, 1000)
上述代码打印的顺序为: 0
-> 执行了onInvalidate,最后执行
-> 1
分析:初始化时先打印count
的值0
, 然后由于定时器把count
的值更新为1
, 此时副作用即将重新执行,因此onInvalidate
的回调函数会被触发,打印执行了onInvalidate
,然后执行了副作用函数,打印count
的值1
。
import { watchEffect, ref } from 'vue' const count = ref(0) const stop = watchEffect((onInvalidate) => { console.log(count.value) onInvalidate(() => { console.log('执行了onInvalidate') }) }) setTimeout(()=> { stop() }, 1000)
上述代码:当我们显示执行stop
函数停止侦听,此时也会触发onInvalidate
的回调函数。同样,watchEffect
所在的组件被卸载时会隐式调用stop
函数停止侦听,故也能触发onInvalidate
的回调函数。
【注意】:
watchEffect
会在 Vue3 开发中大量使用,这里说几个注意点:
- 如果有多个负效应,不要粘合在一起,建议写多个
watchEffect
。
watchEffect(() => { setTimeout(() => console.log(a.val + 1), 1000); setTimeout(() => console.log(b.val + 1), 1000); }); //错误的
这两个 setTimeout 是两个不相关的效应,不需要同时监听 a 和 b,可以分开写
watchEffect(() => { setTimeout(() => console.log(a.val + 1), 1000); }); watchEffect(() => { setTimeout(() => console.log(b.val + 1), 1000); });
2.watchEffect
也可以放在其他生命周期函数内
onMounted(() => { watchEffect(() => { // access the DOM or template refs }); }
总结
watch
懒执行副作用——需要手动指明侦听的内容,也要指明侦听的回调。
默认 immdiate 是 false,所以初始化时不会执行,仅在侦听的源数据变更时才执行回调。
不需要有返回值。
可以获得变化前的值(oldVal)
watchEffect
自动收集依赖,不需要手动传递侦听内容——自动侦听回调函数中使用到的响应式数据
默认 immdiate 是 true,所以初始化时会立即执行,同时源数据变更时也会执行回调。
不需要有返回值。
无法获得变化前的值(oldVal)