软件定制开发供应商Vue3中watch监听对象的属性值,监听源必须是一个getter函数

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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

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}`)  })
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

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)}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

从源码中可以看出,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}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

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

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

  • 如果sourceref类型,getter是个返回source.value的函数,forceTrigger取决于source是否是浅层响应式。
if (isRef(source)) {  getter = () => source.value  forceTrigger = isShallow(source)}
  • 1
  • 2
  • 3
  • 4
  • 如果sourcereactive类型,getter是个返回source的函数,并将deep设置为true。 当直接侦听一个响应式对象时,侦听器会自动启用深层模式
if (isReactive(source)) {  getter = () => source  deep = true}
  • 1
  • 2
  • 3
  • 4

例子

<template>  <div class="container">    <h2>obj---{{ obj }}</h2>    <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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 如果source是个数组,将isMultiSource设为trueforceTrigger取决于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)      }    })}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 如果source是个function。存在cb的情况下,getter函数中会执行source,这里source会通过callWithErrorHandling函数执行,在callWithErrorHandling中会处理source执行过程中出现的错误;不存在cb的话,在getter中,如果组件已经被卸载了,直接return,否则判断cleanupcleanup是在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]      )    }  }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 其他情况getter会被赋值为一个空函数
getter = NOOP__DEV__ && warnInvalidSource(source)
  • 1
  • 2

5.总结

其实,source标准化主要是根据source的类型,将其变成 getter 函数

  • 如果 sourceref对象,则创建一个访问 source.valuegetter函数
  • 如果source是一个reactive对象,则创建一个访问sourcegetter函数,并将deep设置为true
  • 如果source 是一个函数,则会进一步进行判断第二个参数cb是否存在。最后的getter就是一个简单的对source封装的函数
  • 如果source是一个数组,则会对数组中的每个元素进行判断并且返回相应的getter函数。最后返回一个各种getter函数封装成的一个数组

整个doWatch 方法中的逻辑主要分为一下几步:

  1. 通过getter函数来获取数据源的值
  2. 通过job方法来调用传入watch中的cb
    • job中通过调用runnerrunner调用getter获取数据源新值
    • doWatch中闭包缓存了数据源的旧值
    • 将新旧值作为参数调用cb
  3. job作为activeEffectscheduler方法,在后续的数据修改导致的trigger中调用
  4. 首次调用,传入了immediate调用job,未传入调用runner,以数据源为被观察者收集依赖实现响应式

侦听的第一步就是需要通过正确的getter函数去获取侦听源的值,所以在使用watch侦听数据时,务必保证侦听源的类型是符合官方规定的类型的

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发