1. 基本概念
customRef
是 Vue3 提供的用于创建自定义响应式引用的 API,允许显式地控制依赖追踪和触发响应。它返回一个带有 get
和 set
函数的工厂函数来自定义 ref 的行为。
1.1 基本语法
import { customRef } from 'vue'function createCustomRef(value) {return customRef((track, trigger) => {return {get() {track() // 追踪依赖return value},set(newValue) {value = newValuetrigger() // 触发更新}}})
}
2. 常见使用场景
2.1 防抖 Ref
function useDebouncedRef(value, delay = 200) {let timeoutreturn customRef((track, trigger) => {return {get() {track()return value},set(newValue) {clearTimeout(timeout)timeout = setTimeout(() => {value = newValuetrigger()}, delay)}}})
}// 使用示例
const searchQuery = useDebouncedRef('', 500)// 在模板中使用
// <input v-model="searchQuery" />
2.2 节流 Ref
function useThrottledRef(value, delay = 200) {let lastTriggerTime = 0return customRef((track, trigger) => {return {get() {track()return value},set(newValue) {const now = Date.now()if (now - lastTriggerTime >= delay) {value = newValuelastTriggerTime = nowtrigger()}}}})
}// 使用示例
const scrollPosition = useThrottledRef(0, 100)
2.3 验证 Ref
function useValidatedRef(value, validator) {return customRef((track, trigger) => {return {get() {track()return value},set(newValue) {if (validator(newValue)) {value = newValuetrigger()} else {console.warn('Invalid value:', newValue)}}}})
}// 使用示例
const age = useValidatedRef(18, (value) => {return Number.isInteger(value) && value >= 0 && value <= 120
})
2.4 异步 Ref
function useAsyncRef(getter) {let value = nulllet isLoading = trueconst ref = customRef((track, trigger) => {// 初始加载数据getter().then(data => {value = dataisLoading = falsetrigger()})return {get() {track()return { value, isLoading }},set() {throw new Error('Async ref is readonly')}}})return ref
}// 使用示例
const userProfile = useAsyncRef(async () => {const response = await fetch('/api/user')return response.json()
})
3. 高级应用场景
3.1 持久化 Ref
function useLocalStorageRef(key, defaultValue) {const storedValue = JSON.parse(localStorage.getItem(key) || 'null')let value = storedValue !== null ? storedValue : defaultValuereturn customRef((track, trigger) => {return {get() {track()return value},set(newValue) {value = newValuelocalStorage.setItem(key, JSON.stringify(newValue))trigger()}}})
}// 使用示例
const theme = useLocalStorageRef('app-theme', 'light')
3.2 格式化 Ref
function useFormattedRef(value, formatter, parser) {return customRef((track, trigger) => {return {get() {track()return formatter(value)},set(newValue) {value = parser(newValue)trigger()}}})
}// 使用示例
const price = useFormattedRef(1000,(value) => `$${value.toFixed(2)}`,(value) => parseFloat(value.replace('$', ''))
)
4. 实际应用示例
4.1 表单输入处理
<template><div><input v-model="email" /><p>状态: {{ email.status }}</p><p>错误信息: {{ email.error }}</p></div>
</template><script setup>
function useValidatedEmailRef(initialValue = '') {let value = initialValuelet status = 'initial'let error = ''const validateEmail = (email) => {const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/return regex.test(email)}return customRef((track, trigger) => {return {get() {track()return {value,status,error}},set(newValue) {value = newValueif (!newValue) {status = 'initial'error = ''} else if (validateEmail(newValue)) {status = 'valid'error = ''} else {status = 'invalid'error = '请输入有效的邮箱地址'}trigger()}}})
}const email = useValidatedEmailRef()
</script>
4.2 搜索优化
<template><div><input v-model="searchQuery" /><div v-if="searchQuery.isLoading">加载中...</div><ul v-else><li v-for="result in searchQuery.results" :key="result.id">{{ result.title }}</li></ul></div>
</template><script setup>
function useSearchRef(initialValue = '') {let value = initialValuelet results = []let isLoading = falseconst performSearch = async (query) => {if (!query) {results = []return}isLoading = truetry {const response = await fetch(`/api/search?q=${query}`)results = await response.json()} catch (error) {console.error('Search failed:', error)results = []} finally {isLoading = false}}return customRef((track, trigger) => {return {get() {track()return {value,results,isLoading}},set(newValue) {value = newValueperformSearch(newValue).then(() => trigger())}}})
}const searchQuery = useSearchRef()
</script>
5. 最佳实践
5.1 性能优化
// 避免不必要的触发
function useOptimizedRef(value) {return customRef((track, trigger) => {return {get() {track()return value},set(newValue) {// 只在值真正改变时触发更新if (value !== newValue) {value = newValuetrigger()}}}})
}
5.2 错误处理
function useSafeRef(value, errorHandler = console.error) {return customRef((track, trigger) => {return {get() {try {track()return value} catch (error) {errorHandler(error)return null}},set(newValue) {try {value = newValuetrigger()} catch (error) {errorHandler(error)}}}})
}
6. 注意事项
- 避免过度使用
// ❌ 不要为简单的值使用 customRef
const simpleValue = customRef((track, trigger) => ({get() {track()return value},set(newValue) {value = newValuetrigger()}
}))// ✅ 使用普通的 ref
const simpleValue = ref(value)
- 保持响应性
// 确保在需要的时候调用 track 和 trigger
function useCustomRef(value) {return customRef((track, trigger) => ({get() {track() // 不要忘记 trackreturn value},set(newValue) {value = newValuetrigger() // 不要忘记 trigger}}))
}
- 内存管理
// 清理副作用
function useCustomRef(value) {let cleanup = nullreturn customRef((track, trigger) => ({get() {track()return value},set(newValue) {// 清理之前的副作用if (cleanup) {cleanup()}value = newValue// 设置新的副作用cleanup = setupSideEffect(value)trigger()}}))
}