深入vue3的watch & watchEffect

1.watch

基本用法

在vue中需要根据某些响应式数据的变化而进行特定复杂逻辑(网络请求、定时任务、操作dom等)的处理时,便可以采用watch函数:

const watchState = ref(0)

watch(watchState, (newValue, oldValue) => {
    // 复杂逻辑处理
})

watch的简单用法如上图所述,当响应式数据watchState变化的时候,传入watch的回调函数便会自动执行,该回调函数可以接收到watchState变化前后的值。

watch函数的第一参数即监听的数据源支持以下几种格式:

  1. ref
  2. rective
  3. 计算属性
  4. getter
  5. 上述三种形式组合成的数组
// 写法1
const a = ref(0)
watch(a, (newValue, oldValue) => {})

// 写法2
const b = reactive({name: '张三', age: 11})
// 默认开启了deep的深度监听器,所有嵌套属性改变都会触发
watch(b, (newValue, oldValue) => {})

// 写法3
const comp = computed(() => {
    ...
})
watch(comp, (newVal, oldVal) => {})

// 写法4
const getter = () => b.name
watch(getter, (newValue, oldValue) => {})
// 下面的写法是错误的,因为该写法相当于直接把一个字符串放到了watch里面,若想监听reactive对象的某个属性,需要采用上面的getter写法
watch(b.name, () => {})

// 写法5
const arr = [a, b, comp, getter]
watch(arr, ([newA, newB, newComp, newGetter], [oldA, oldB, oldComp, oldGetter]) => {})

副作用清理

watch的回调函数除了接收监听数据的新值和旧值,还可以接受一个清理函数onCleanUp,该函数主要用于清理回调函数可能产生的副作用,watch也会返回一个函数stop,可帮助用户自定义时刻停止watch监听逻辑

const a = ref(1)
const stop = watch(a, (newVal, oldVal, onCleanUp) => {
  const intervalId = setInterval(() => {
    console.log(newVal)
  }, 1000)
  onCleanUp(function clear() {
    console.log('清理')
    clearInterval(intervalId)
  })
})

如上述代码展示,回调函数接收了三个参数,其中第三个参数便是清理函数,在回调函数中声明了一个定时任务,然后调用了onCleanUp并传入了清理定时任务的clear函数,clear函数的调用时机包括:

  • 回调函数下一次执行前自动调用
  • 在同步语句中声明的watch会在组件卸载时自动调用
  • 手动调用stop函数

第一个时机表示clear函数会在回调函数执行的第n次之前调用(n=2, 3, 4....),第二个时机需要特别注意同步语句,与之相对应的即在异步语句中声明的watch在组件卸载的时候不会自动清理

<script setup>
// watch1
watch(source, (newVal, oldVal, onCleanUp) => {
    onCleanUp(() => {}) // 自动调用
})

// watch2
Promise.resolve().then(() => {
    watch(source, (newVal, oldVal, onCleanUp) => {
        onCleanUp(() => {}) // 不会自动调用
    })
})

//watch3
setTimeout(() => {
    watch(source, (newVal, oldVal, onCleanUp) => {
        onCleanUp(() => {}) // 不会自动调用
    })
})
</script>

第三个时机的手动清理比较好理解,例如可以在用户触发某个点击事件的时候调用stop函数,触发停止监听并执行清理功能

配置项

watch函数可以传递三个参数,分别是监听的数据源、回调函数以及配置项,其中配置项支持参数包括:deepimmediateflush

深度监听

其中deep表示是否开启深度监听,针对一个reactive对象,如何采用watch对其进行监听,默认会开启深度监听,一个返回响应式对象的 getter 函数,只有在返回不同的对象时,才会触发回调,只有加上了deep: true的配置才会转换成深度监听

watch(() => state.obj, () => {}, {
    deep: true
})

立即执行

watch默认是懒执行的,仅当数据源变化时,才会执行回调,如果希望在建立监听器的时候,立即执行一次回调函数,可以通过设置immediate: true来实现回调函数的立即执行

watch(source, () => {}, { immediate: true })

执行时机

watch回调函数会被批量处理以避免重复调用,如下的代码,触发了changeRefA事件之后,a的值被连续更改了三次,但watch回调函数中的打印只会执行一次,如果希望watch的回调同步执行可以设置参数flush: 'sync'

const a = ref(1)

watch(a, () => {
    console.log('批量执行') // 打印一次
})

watch(a, val => {
    console.log('同步执行') // 打印三次
}, { flush: 'sync' })

// 点击事件,修改a的值
const changeRefA = () => {
    a.value++
    a.value++
    a.value++
}

按照vue官方文档说法:默认情况下,侦听器回调会在父组件更新 (如有) 之后、所属组件的 DOM 更新之前被调用。这意味着如果你尝试在侦听器回调中访问所属组件的 DOM,那么 DOM 将处于更新前的状态。

需要注意的点是:默认情况下,在回调函数中获取的所属组件的dom是更新之前的dom,如果希望获取dom更新之后的状态,可以设置参数flush: 'post',可以获得类似nextTick的效果

<template>
   <div>
      <button @click='showButton'>show button</button>
      <button v-if='show' id='btn'></button>
   </div>
</template>

<script setup>
const show = ref(false)
const showButton = () => {
    show.value = true
}

watch(show, (val) => {
    if (val) {
        document.getElementById('btn') // 点击show button按钮,触发该回调函数执行,但此时的dom为null
    }
})

watch(show, (val) => {
    if (val) {
        document.getElementById('btn') // 此时的值为页面button的dom
    }
}, { flush: 'post' })
</script>

2.watchEffect

基本用法

watchEffect允许自动追踪回调函数中的响应式数据,写法如下

watch(params, async val => {
    await request.post('', params)
})

// 不需要特定写明监听params,param改变,会自动重新请求数据
watchEffect(async () => {
    await request.post('', params)
})

watchEffectwatch的懒执行不同,默认会执行一次,相当于默认设置了immediate: true

副作用清理

watchEffect的副作用清理主要由回调函数的参数完成,方式和watch相同,清理函数的调用时机也和watch相同

watchEffect((onCleanUp) => {
    onCleanUp(() => {
        // 清理逻辑
    })
})

配置项

watchEffect的配置项参数主要是flushwatchEffect默认实现了立即执行和对使用过的属性自动追踪的效果,因此,无需immediatedeep参数。针对flush参数,watchEffect实现的效果和watch相同,vue还提供了对应的简写api

watchEffect(callback, { flush: 'post' }) 
watchPostEffect(callback)

watchEffect(callback, { flush: 'sync' })
watchSyncEffect(callback)

watch vs watchEffect

相同点

主要功能相同,都可以响应地执行回调函数,完成复杂逻辑的处理

不同点

  1. watch默认是懒执行的,watchEffect会在初始定义时执行一次
  2. watch只会追踪明确定义的数据源,不会追踪回调函数中使用到的数据源,可以更精确地控制回调函数的执行时机;watchEffect不需要明确定义需要追踪的数据源,可以自动追踪回调函数中使用到的响应式数据,代码更加简洁,但依赖关系不如watch明确
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值