vue3中customRef的用法以及使用场景
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 = newValue
trigger() // 触发更新
}
}
})
}
2. 常见使用场景
2.1 防抖 Ref
function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}
})
}
// 使用示例
const searchQuery = useDebouncedRef('', 500)
// 在模板中使用
// <input v-model="searchQuery" />
2.2 节流 Ref
function useThrottledRef(value, delay = 200) {
let lastTriggerTime = 0
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
const now = Date.now()
if (now - lastTriggerTime >= delay) {
value = newValue
lastTriggerTime = now
trigger()
}
}
}
})
}
// 使用示例
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 = newValue
trigger()
} 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 = null
let isLoading = true
const ref = customRef((track, trigger) => {
// 初始加载数据
getter().then(data => {
value = data
isLoading = false
trigger()
})
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 : defaultValue
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
value = newValue
localStorage.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 = initialValue
let 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 = newValue
if (!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 = initialValue
let results = []
let isLoading = false
const performSearch = async (query) => {
if (!query) {
results = []
return
}
isLoading = true
try {
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 = newValue
performSearch(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 = newValue
trigger()
}
}
}
})
}
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 = newValue
trigger()
} catch (error) {
errorHandler(error)
}
}
}
})
}
6. 注意事项
- 避免过度使用
// ❌ 不要为简单的值使用 customRef
const simpleValue = customRef((track, trigger) => ({
get() {
track()
return value
},
set(newValue) {
value = newValue
trigger()
}
}))
// ✅ 使用普通的 ref
const simpleValue = ref(value)
- 保持响应性
// 确保在需要的时候调用 track 和 trigger
function useCustomRef(value) {
return customRef((track, trigger) => ({
get() {
track() // 不要忘记 track
return value
},
set(newValue) {
value = newValue
trigger() // 不要忘记 trigger
}
}))
}
- 内存管理
// 清理副作用
function useCustomRef(value) {
let cleanup = null
return customRef((track, trigger) => ({
get() {
track()
return value
},
set(newValue) {
// 清理之前的副作用
if (cleanup) {
cleanup()
}
value = newValue
// 设置新的副作用
cleanup = setupSideEffect(value)
trigger()
}
}))
}