提升 Vue 项目开发效率:用 useWatchFields 轻松监听指定字段变化

在 Vue 3 的开发中,随着应用变得越来越复杂,响应式状态管理成为了不可或缺的一部分。尤其是当你的 state 对象越来越庞大时,你可能只关心其中某些字段的变化。这时,传统的 watch 和 computed API 在使用上可能显得有些繁琐且不够灵活。
为了帮助开发者更高效地监听和响应字段的变化,我们推出了一个全新的 Vue 3 自定义 Hook —— useWatchFields。这个 Hook 可以让你灵活地监听 state 中的某些指定字段变化,并且不依赖于任何外部库,完全通过 Vue 内置的 API 来实现事件的管理。

为什么使用 useWatchFields?

当你在开发中只对部分字段的变化感兴趣时,往往需要为每个字段单独设置 watch,但这样做不仅代码冗长,而且不够灵活。useWatchFields 可以帮助你精确监听特定字段的变化,并自动将变化的前后值传递给回调函数。

如何使用 useWatchFields?

假设你有一个包含多个字段的 state 对象,并且你只关心其中某些字段的变化,例如 name 和 age 字段。使用 useWatchFields 后,你可以像下面这样轻松实现:

import { ref } from 'vue'
import { useWatchFields } from './useWatchFields'

// 假设你的 state 包含多个字段
const state = ref({
  name: 'John Doe',
  age: 30,
  email: 'john@example.com',
})

// 监听 name 和 age 字段的变化
const { onChange } = useWatchFields(state, ['name', 'age'])

// 注册事件监听器,获取字段变化信息
onChange((data) => {
  console.log('变化字段:', data.changedFields) // ['name', 'age']
  console.log('字段变化前后值:', data.fieldChangeMap)
})

通过简单的几行代码,你就可以开始监听字段的变化,并且在变化时获得详细的字段变化信息!

useWatchFields 的核心功能

灵活指定监听字段:

只需传入字段名数组,useWatchFields 会自动监听这些字段的变化,不需要处理整个 state 对象。

高效的事件触发机制:
通过 ref 手动管理事件监听器,使得事件管理更加轻量。

获取字段的变化信息:
当字段发生变化时,useWatchFields 会返回一个对象,包含哪些字段发生了变化、变化前后的值等详细信息,帮助你更精准地捕捉字段的变动。

防抖功能
useWatchFields 还支持防抖功能,避免频繁触发事件。例如,在表单输入或快速变化的字段中,防抖能够有效地减少不必要的性能消耗。你可以通过传递 debounceTimeout 来控制防抖的延迟,单位为毫秒。

const { onChange } = useWatchFields(state, ['name', 'age'], { debounceTimeout: 500 })

适用场景

表单处理:当你只关心表单中的某些输入字段的变化时,useWatchFields 可以大大简化你的代码结构。
动态数据更新:例如在用户数据管理、购物车状态等场景中,你可以使用 useWatchFields 精确地监听特定字段的变化。
条件渲染:当你需要根据某些字段的变化来触发视图更新时,这个 Hook 也能帮助你高效完成。

无论是小型应用还是大型复杂项目中的数据管理,useWatchFields 都能为你带来极大的便利。它让你可以灵活监听 state 中指定字段的变化,避免冗长的 watch 代码,并提供详细的变化信息。直接使用 Vue 内置的 API,代码更加简洁、轻量。

import { isReactive, isRef, ref, watch } from 'vue'

// 定义返回对象的类型
export interface WatchFieldsResult<T> {
  /** 发生变化的字段数组 */
  changedFields: Array<keyof T>
  /** 所有监听的字段数组 */
  fields: Array<keyof T>
  /** 每个字段的变化前后值 */
  fieldChangeMap: Partial<Record<keyof T, { newValue: T[keyof T] | undefined, oldValue: T[keyof T] | undefined }>>
}

/**
 * 自定义 Hook,用于监听 `state` 中指定字段的变化。
 *
 * @params state - 一个 `Ref` 或 `reactive` 类型的响应式对象,它的字段值会被监听。
 * @params fields - 一个字符串数组,指定要监听的字段名。
 * @params options.debounceDelay - 防抖延迟时间,单位毫秒。默认为 300ms,传递 `undefined` 或 `0` 时关闭防抖
 * @params options.immediate - 是否在初始化时立即执行一次回调函数,默认为 `false`。
 *
 * @returns onChange - 一个触发事件的函数,可以用于获取字段变化的详细信息。
 */
export function useWatchFields<T extends object>(
  state: Ref<T> | T,
  fields: Array<keyof T>,
  options?: {
  /** 可选参数,防抖延迟时间 */
    debounceDelay?: number
    /** 初始化执行 */
    immediate?: boolean
  },
): { onChange: (listener: (data: WatchFieldsResult<T>) => void) => void } {
  const listeners = ref<((data: WatchFieldsResult<T>) => void)[]>([]) // 存储监听回调函数
  let debounceTimeout: ReturnType<typeof setTimeout> | null = null // 防抖定时器

  // 手动触发事件的函数
  const trigger = (data: WatchFieldsResult<T>) => {
    listeners.value.forEach(listener => listener(data))
  }

  // 防抖函数:延迟触发
  const debouncedTrigger = (data: WatchFieldsResult<T>) => {
    if (debounceTimeout) {
      // 清除上一个定时器
      clearTimeout(debounceTimeout)
    }
    // 设置新的定时器
    debounceTimeout = setTimeout(() => trigger(data), options!.debounceDelay!)
  }

  // 使用 Vue 的 watch API 监听多个字段的变化
  watch(
    () => fields.map((field) => {
      if (isRef(state)) {
        // 如果是 Ref 类型,返回对应字段的值
        return state.value[field]
      } else if (isReactive(state)) {
        // 如果是 reactive 类型,返回对应字段的值
        return state[field]
      }
      return undefined
    }),
    (newValues, oldValues) => {
      // 用于存储发生变化的字段
      const changedFields: (keyof T)[] = []
      // 用于存储每个字段的变化前后值
      const fieldChangeMap: WatchFieldsResult<T>['fieldChangeMap'] = {}

      newValues.forEach((newValue, index) => {
        // 获取当前字段
        const field = fields[index]
        // 获取字段的旧值
        const oldValue = oldValues[index]

        // 如果新值与旧值不同,表示字段发生了变化
        if (newValue !== oldValue) {
          changedFields.push(field)
          fieldChangeMap[field] = { newValue, oldValue }
        }
      })

      const data: WatchFieldsResult<T> = {
        changedFields,
        fields,
        fieldChangeMap,
      }

      // 根据 debounceDelay 的值判断是否启用防抖
      if (options?.debounceDelay && options?.debounceDelay > 0) {
        debouncedTrigger(data) // 启用防抖
      } else {
        trigger(data) // 直接触发,不启用防抖
      }
    },
    { deep: true, immediate: options?.immediate },
  )

  // 清理防抖定时器
  onUnmounted(() => {
    if (debounceTimeout) {
      clearTimeout(debounceTimeout) // 组件销毁时清除定时器
    }
  })

  // 返回一个注册回调的函数
  return {
    onChange: (listener: (data: WatchFieldsResult<T>) => void) => {
      listeners.value.push(listener)
    },
  }
}

作者:ErvinHowell
链接:https://juejin.cn/post/7473678614366961699
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

在下次开发时,不妨尝试一下 useWatchFields,它将是你提高开发效率、简化代码的得力助手。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值