深入理解 Vue3 自定义 Ref:使用 customRef 实现精细化响应式控制
·
一、什么是 customRef?
customRef 是 Vue3 提供的一个高级 API,它允许开发者创建自定义的响应式引用,并对其依赖追踪和更新触发进行精细化的逻辑控制。与普通的 ref 相比,customRef 赋予了我们对响应式过程更底层的控制能力。
二、基本语法和工作原理
customRef 接收一个工厂函数,该函数接受 track 和 trigger 两个回调函数作为参数,并返回一个包含 get 和 set 方法的对象:
import { customRef } from 'vue'
const myCustomRef = customRef((track, trigger) => {
return {
get() {
// 读取值时调用
track() // 依赖追踪:告诉Vue数据msg很重要,要对msg持续关注,一旦变化就更新
return value
},
set(newValue) {
// 设置值时调用
value = newValue
trigger() // 触发更新:通知Vue数据msg变化了
}
}
})
//如果他们两个其中省略一个会产生不一样的效果下面一个表格
总结表格
| 情况 | track() | trigger() | 结果 | 适用场景 |
|---|---|---|---|---|
| 正常使用 | √ | √ | 完整的响应式 | 大多数情况 |
| 省略 track | ❌ | √ | 数据变但界面不更新 | 几乎不用 |
| 省略 trigger | √ | ❌ | 依赖追踪但无更新 | 手动控制更新 |
| 两者都省略 | ❌ | ❌ | 完全非响应式 | 不应该使用 |
三、实战:防抖 Ref 实现
让我们通过一个实际的防抖案例来理解 customRef 的强大之处:
// useDebouncedRef.ts
import { customRef } from 'vue'
export default function useDebouncedRef<T>(initialValue: T, delay = 200) {
let timer: NodeJS.Timeout
return customRef<T>((track, trigger) => {
return {
get() {
track() // 声明依赖追踪
return initialValue
},
set(value) {
clearTimeout(timer)
timer = setTimeout(() => {
initialValue = value
trigger() // 触发组件更新
}, delay)
}
}
})
}
在组件中使用:
vue
<template>
<div>
<input v-model="searchText" placeholder="搜索..." />
<p>实时显示: {{ searchText }}</p>
</div>
</template>
<script setup lang="ts">
import useDebouncedRef from './useDebouncedRef'
// 创建防抖响应式引用,延迟 500ms
const searchText = useDebouncedRef('', 500)
</script>
四、customRef vs 普通 ref:对比分析
1. 控制粒度不同
普通 ref:
const count = ref(0)
// 更新立即触发响应式
count.value++ // 立即更新组件
customRef:
const debouncedCount = useDebouncedRef(0, 1000)
// 更新可延迟触发
debouncedCount.value++ // 1秒后才更新组件
2. 适用场景对比
| 场景 | 普通 ref | customRef |
|---|---|---|
| 简单状态管理 | 完美适用 | 过于复杂 |
| 防抖/节流 | 需要额外代码 | 内置实现 |
| 异步更新控制 | 有限制 | 完全控制 |
| 复杂数据转换 | 需要计算属性 | 直接处理 |
更多实用场景
场景一:缓存数据请求
// useCachedRef.ts
export function useCachedRef<T>(key: string, initialValue: T, ttl = 300000) {
return customRef<T>((track, trigger) => {
// 尝试从 localStorage 读取缓存
const cached = localStorage.getItem(key)
let value = initialValue
if (cached) {
const { data, timestamp } = JSON.parse(cached)
if (Date.now() - timestamp < ttl) {
value = data
}
}
return {
get() {
track()
return value
},
set(newValue) {
value = newValue
// 保存到缓存
localStorage.setItem(key, JSON.stringify({
data: newValue,
timestamp: Date.now()
}))
trigger()
}
}
})
}
场景二:验证输入
// useValidatedRef.ts
export function useValidatedRef(
initialValue: string,
validator: (value: string) => boolean
) {
let value = initialValue
let isValid = validator(initialValue)
return customRef<string>((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
const wasValid = isValid
value = newValue
isValid = validator(newValue)
// 只在值有效或有效性状态改变时触发更新
if (isValid || wasValid !== isValid) {
trigger()
}
}
}
})
}
最佳实践和注意事项
1. 内存管理
// 清理定时器防止内存泄漏
export function useAutoCleanupRef<T>(initialValue: T) {
let timers: number[] = []
const ref = customRef<T>((track, trigger) => {
return {
get() {
track()
return initialValue
},
set(value) {
initialValue = value
trigger()
}
}
})
// 提供清理方法
const cleanup = () => {
timers.forEach(timer => clearTimeout(timer))
timers = []
}
return { ref, cleanup }
}
2. 错误处理
export function useSafeCustomRef<T>(initialValue: T) {
return customRef<T>((track, trigger) => {
return {
get() {
try {
track()
return initialValue
} catch (error) {
console.error('Error in customRef getter:', error)
return initialValue
}
},
set(value) {
try {
initialValue = value
trigger()
} catch (error) {
console.error('Error in customRef setter:', error)
}
}
}
})
}
总结
customRef 是 Vue3 响应式系统中一个强大而灵活的工具,它为我们提供了:
-
精细控制:完全掌控依赖追踪和更新触发时机
-
逻辑封装:将复杂响应式逻辑封装为可复用函数
-
性能优化:实现防抖、节流、缓存等性能优化模式
-
业务解耦:将业务逻辑与组件代码分离
如果面试的时候,hr问你customRef为什么会精细控制,其实要你的是trigger(触发更新)和track(依赖追踪) 很重要。
虽然 customRef 提供了更大的灵活性,但也带来了更高的复杂性。在简单场景下,优先考虑使用普通的 ref 和 computed,只有在需要特殊控制逻辑时才选择 customRef。
更多推荐
所有评论(0)