Vue3中怎么使用watch监听对象的属性值

寻技术 VUE 2023年11月25日 120

这篇文章主要介绍“Vue3中怎么使用watch监听对象的属性值”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Vue3中怎么使用watch监听对象的属性值”文章能帮助大家解决问题。

Vue3 中使用 watch 侦听对象中的具体属性

1.前言

<script lang="ts" setup>
	// 接受父组件传递的数据
    const props = defineProps({
        test: {
            type: String,
            default: ''
        }
    })
    
    // 使用 watch 侦听 props 中的 test 属性
    watch(
        // 这种写法不会侦听到 props 中 test 的变化
    	props.test,
        () => {
            console.log("侦听成功")
        }
    )
    
    watch(
    	// 这种写法会侦听到 props 中 test 的变化
        () => props.test,
        () => {
            console.log("侦听成功")
        }
    )
</script>

watch 的基本用法

watch() 默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数

第一个参数:侦听源,侦听源可以是一下几种

一个函数,返回一个值一个 ref一个响应式对象(reactive)或是由以上类型的值组成的数组

第二个参数:侦听源发生变化时要触发的回调函数。

(newValue, oldValue) => { /* code */}

当侦听多个来源时,回调函数接受两个数组,分别对应源数组中的新值和旧值

( [ newValue1, newValue2 ] , [ oldValue1 , oldValue2 ]) => {/* code */}

第三个参数:可选对象,可以支持一下这些选项

immediate:侦听器创建时立即触发回调deep:如果源是一个对象,会强制深度遍历,以便在深层级发生变化时触发回调函数flush:调整回调函数的刷新时机onTrack / onTrigger:调试侦听器的依赖

2. 原因

因为

watch
的侦听源只能是上面的4中情况
const obj = reactive({ count: 0 })

// 错误,因为 watch() 中的侦听源是一个 number,最终 source 返回的 getter 函数是一个空,所以就得不到侦听的数据
watch(obj.count, (count) => {
  console.log(`count is: ${count}`)
})

// 正确,主要思想是,将侦听源转化为以上4种类型(转化为getter函数是最简单方便的)
watch(
  () => obj.count,
  (count) => {
    console.log(`count is: ${count}`)
  }
)

3.watch源码分析

export function watch<T = any, Immediate extends Readonly<boolean> = false>(
  source: T | WatchSource<T>,
  cb: any,
  options?: WatchOptions<Immediate>
): WatchStopHandle {
  if (__DEV__ && !isFunction(cb)) {
    warn(
      ``watch(fn, options?)` signature has been moved to a separate API. ` +
      `Use `watchEffect(fn, options?)` instead. `watch` now only ` +
      `supports `watch(source, cb, options?) signature.`
    )
  }
  return doWatch(source as any, cb, options)
}

从源码中可以看出,

watch
接收三个参数:
source
侦听源、
cb
回调函数、
options
侦听配置,最后会返回一个
doWatch

4.doWatch源码分析

function doWatch(
  source: WatchSource | WatchSource[] | WatchEffect | object,
  cb: WatchCallback | null,
  { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
): WatchStopHandle {
  // ...
// 当前组件实例
const instance = currentInstance
// 副作用函数,在初始化effect时使用
let getter: () => any
// 强制触发侦听
let forceTrigger = false
// 是否为多数据源。
let isMultiSource = false
}

doWatch
依然接受三个参数:
source
侦听源、
cb
回调函数、
options
侦听配置

这里着重对侦听源的源码进行分析(source标准化

  • 如果

    source
    ref
    类型,
    getter
    是个返回
    source.value
    的函数,
    forceTrigger
    取决于
    source
    是否是浅层响应式。
if (isRef(source)) {
  getter = () => source.value
  forceTrigger = isShallow(source)
}
  • 如果

    source
    reactive
    类型,
    getter
    是个返回
    source
    的函数,并将
    deep
    设置为
    true
    。 当直接侦听一个响应式对象时,侦听器会自动启用深层模式
if (isReactive(source)) {
  getter = () => source
  deep = true
}

例子

<template>
  <div class="container">
    <h3>obj---{{ obj }}</h3>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
  </div>
</template>

<script lang="ts" setup>
import { reactive, watch } from "vue";
const obj = reactive({
  name: "张三",
  age: 18,
});
const changeName = () => {
  obj.name += "++";
};
const changeAge = () => {
  obj.age += 1;
};
// obj 中的任一属性变化了,都会被监听到
watch(obj, () => {
  console.log("变化了");
});
</script>
  • 如果

    source
    是个数组,将
    isMultiSource
    设为
    true
    forceTrigger
    取决于
    source
    是否有
    reactive
    类型的数据,
    getter
    函数中会遍历
    source
    ,针对不同类型的
    source
    做不同处理。
if (isArray(source)) {
  isMultiSource = true
  forceTrigger = source.some(isReactive)
  getter = () =>
    source.map(s => {
      if (isRef(s)) {
        return s.value
      } else if (isReactive(s)) {
        return traverse(s)
      } else if (isFunction(s)) {
        return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
      } else {
        __DEV__ && warnInvalidSource(s)
      }
    })
}
  • 如果

    source
    是个
    function
    。存在
    cb
    的情况下,
    getter
    函数中会执行
    source
    ,这里
    source
    会通过
    callWithErrorHandling
    函数执行,在
    callWithErrorHandling
    中会处理
    source
    执行过程中出现的错误;不存在
    cb
    的话,在
    getter
    中,如果组件已经被卸载了,直接
    return
    ,否则判断
    cleanup
    cleanup
    是在
    watchEffect
    中通过
    onCleanup
    注册的清理函数),如果存在
    cleanup
    执行
    cleanup
    ,接着执行
    source
    ,并返回执行结果。
    source
    会被
    callWithAsyncErrorHandling
    包装,该函数作用会处理
    source
    执行过程中出现的错误,与
    callWithErrorHandling
    不同的是,
    callWithAsyncErrorHandling
    会处理异步错误。
if (isFunction(source)) {
  if (cb) {
    getter = () =>
      callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
  } else {
    // watchEffect
    getter = () => {
      // 如果组件实例已经卸载,直接return
      if (instance && instance.isUnmounted) {
        return
      }
      // 如果清理函数,则执行清理函数
      if (cleanup) {
        cleanup()
      }
      // 执行source,传入onCleanup,用来注册清理函数
      return callWithAsyncErrorHandling(
        source,
        instance,
        ErrorCodes.WATCH_CALLBACK,
        [onCleanup]
      )
    }
  }
}
  • 其他情况

    getter
    会被赋值为一个空函数
getter = NOOP
__DEV__ && warnInvalidSource(source)
关闭

用微信“扫一扫”