当前位置: 首页 > 科技观察

终于彻底理解了Watch和WatchEffect,原来功能这么强大!

时间:2023-03-17 23:29:12 科技观察

以前以为可以用watch和watchEffect,后来发现只是粗浅的了解。最近整理了Vue3的监听器,分享给大家。看看有没有什么不懂的,一起来学习吧!Watch的基本用法当我们需要在数据变化时执行一些“副作用”:比如更改DOM和执行异步操作时,我们可以使用watch函数:watch()一共可以接受三个参数,监听数据源、回调函数和配置选项。watch数据源watch的第一个参数可以是不同形式的“数据源”,可以是:ref、计算属性、getter函数(有返回值的函数)、响应对象、数组以上类型的值constx=ref(1)consty=ref(1)constdoubleX=computed(()=>x.value*2)constobj=reactive({count:0})//singlerefwatch(x,(newValue)=>{console.log(`xis${newValue}`)})//计算属性watch(doubleX,(newValue)=>{console.log(`doubleXis${newValue}`)})//getter函数watch(()=>x.value+y.value,(sum)=>{console.log(`sumofx+yis:${sum}`)})//responsiveobjectwatch(obj,(newValue,oldValue)=>{//当嵌套属性改变时触发//注意:这里的`newValue`等于`oldValue`//因为它们是同一个对象!})//以上类型值数组watch([x,()=>y.value],([newX,newY])=>{console.log(`xis${newX}andyis${newY}`)})请注意,您不能直接收听属性v反应对象的值,例如:constobj=reactive({count:0})//错误,因为watch()获取的参数是一个数字watch(obj.count,(count)=>{console.log(`countis:${count}`)})这里需要使用一个返回属性的getter函数://Provideagetterfunctionwatch(()=>obj.count,(count)=>{console.log(`计数是:${count}`)})回调函数watch的第二个参数是数据变化时执行的回调函数。此回调函数接受三个参数:新值、旧值和用于清理副作用的回调函数。这个回调函数会在下次执行sideeffect之前调用,可以用来清除无效的sideeffect,比如等待异步请求:constid=ref(1)constdata=ref(null)watch(id,async(newValue,oldValue,onCleanup)=>{const{response,cancel}=doAsyncWork(id.value)//当`id`改变时调用`cancel`//取消之前未完成的请求onCleanup(cancel)data.value=awaitresponse.json()})watch的返回值是一个用来停止副作用的函数:constunwatch=watch(()=>{})//...unwatch()whenthelisteneris不再需要注意:使用同步语句创建的监听器会自动绑定到宿主组件实例上,当宿主组件卸载时会自动停止。使用异步回调创建侦听器不会绑定到当前组件,您必须手动停止它以防止内存泄漏。如下例:配置选项watch的第三个参数是可选对象,支持以下选项:immediate:在监听器中创建回调是立即触发。deep:深度遍历,以便在进行深层更改时触发回调。flush:回调函数的触发时机。pre:默认,dom更新前调用,post:dom更新后调用,sync同步调用。onTrack/onTrigger:用于调试的挂钩。在收集依赖项并触发回调时调用。deeplistener直接传入一个reactive对象给watch(),默认会创建一个deeplistener——所有嵌套的属性在变化时都会被触发:constobj=reactive({count:0})watch(obj,(newValue,oldValue)=>{//当嵌套属性改变时触发//注意:这里的`newValue`等于`oldValue`//因为它们是同一个对象!})obj.count++相反,一个getter函数返回一个反应对象仅在对象被替换时触发:constobj=reactive({someString:'hello',someObject:{count:0}})watch(()=>obj.someObject,()=>{//仅触发当obj.someObject被替换时})当然,你也可以显式地添加deep选项来强制深度监听:watch(()=>obj.someObject,(newValue,oldValue)=>{//这里的`newValue`是等于`oldValue`//除非obj.someObject被完全替换console.log('deep',newValue.count,oldValue.count)},{deep:true})obj.someObject.count++//deep11监听反应对象ect或数组,新值等于旧值。为了解决这个问题,我们可以做值??的深拷贝。watch(()=>_.cloneDeep(obj.someObject),(newValue,oldValue)=>{//此时`newValue`不等于`oldValue`console.log('deep',newValue.count,oldValue.count)},{deep:true})obj.someObject.count++//deep10注意:深度监听需要遍历所有嵌套属性,当数据结构庞大时,开销非常大。所以我们要谨慎使用,注意性能。watchEffectwatch()是惰性的:回调只会在数据源发生变化时执行。但是在某些场景下,我们希望在创建监听器时立即执行回调。当然也可以使用immediate选项来实现:consturl=ref('https://...')constdata=ref(null)asyncfunctionfetchData(){constresponse=awaitfetch(url.value)data.value=awaitresponse.json()}//立即执行一次,然后监听url变化watch(url,fetchData,{immediate:true})可以看到watch使用了三个参数,我们可以使用watchEffect来简化上面的代码。watchEffect会立即执行一次回调函数。如果此时函数产生了副作用,Vue会自动跟踪副作用的依赖关系,自动分析监听的数据源。上面的例子可以重写为:consturl=ref('https://...')constdata=ref(null)//一个参数可以处理watchEffect(async()=>{constresponse=awaitfetch(url.value)data.value=awaitresponse.json()})watchEffect接受两个参数,第一个参数是数据变化时执行的回调函数,用法同watch。第二个参数是可选对象,支持flush和onTrack/onTrigger选项,功能同watch。注意:watchEffect只会在其同步执行期间跟踪依赖项。使用异步回调时,只会跟踪在第一次await之前访问的依赖项。watchvs.watchEffectwatch和watchEffect的主要功能是一样的,都可以响应式的方式执行回调函数。它们的区别在于追踪响应式依赖的方式:watch只追踪明确定义的数据源,不追踪回调中访问了什么;默认情况下,回调仅在数据源发生变化时触发;watch可以访问监听数据的新旧值。watchEffect会被初始化一次,在sideeffects期间跟踪依赖关系,并自动分析监听的数据源;watchEffect无法访问监听数据的新旧值。总而言之,watch更强大,watchEffect在某些场景下更简洁。