原创 ErvinHowell 2025-03-08 09:01 重庆
点击关注公众号,“技术干货” 及时达!
在 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)
},
}
}
在下次开发时,不妨尝试一下 useWatchFields
,它将是你提高开发效率、简化代码的得力助手。
点击关注公众号,“技术干货” 及时达!