这一节了解一下Vue3 中的自定义引用,customRef是Vue3提供的一个响应式API,允许你自定义ref的get和set内部逻辑,通过track()收集依赖、trigger()触发更新,从而实现防抖等特殊行为。简单总结如下:
API:
customRef(factory):创建一个自定义的ref,传入一个工厂函数factory
factory 固定参数:track,trigger
track():收集依赖->当数据被模板/计算属性读取时,通知Vue收集当前副作用
trigger():派发更新 ->当数据修改完成后,通知所有收集到的依赖去更新视图
返回值:一个ref对象
场景:
1 防抖,输入框延迟更新,减少频繁请求
2 带本地持久化的ref:读写同时自动存入localStorage
3 读写拦截、数据校验
栗子:
TypeScript
<template>
<div>
<input v-model="searchKey" placeholder="输入搜索内容" />
<p>实时防抖结果:{{ searchKey }}</p>
</div>
</template>
<script setup>
import { customRef } from 'vue'
function useDebounceRef(initValue, delay = 500) {
let timer = null
// customRef 工厂函数,接收 track、trigger
return customRef((track, trigger) => {
let value = initValue
return {
get() {
track()
return value
},
set(newVal) {
clearTimeout(timer)
timer = setTimeout(() => {
value = newVal
trigger()
}, delay)
}
}
})
}
// 使用自定义ref
const searchKey = useDebounceRef('')
</script>
Demo02
TypeScript
<template>
<div>
<input v-model="username" placeholder="输入用户名" />
<p>当前用户名:{{ username }}</p>
<button @click="assignIllegal">赋值非法数字</button>
</div>
</template>
<script setup>
import { customRef } from 'vue'
function useStorageRef(key, defaultValue) {
return customRef((track, trigger) => {
let value = localStorage.getItem(key) ?? defaultValue
return {
get() {
console.log('[读取数据]', value)
track()
return value
},
set(newVal) {
console.log('[准备赋值]', newVal)
if (typeof newVal !== 'string') {
console.warn('赋值失败:数据类型不合法,只允许字符串!')
return
}
value = newVal
localStorage.setItem(key, value)
trigger()
console.log('[赋值成功并持久化]', value)
}
}
})
}
// 使用自定义引用
const username = useStorageRef('user_name', '默认用户')
const assignIllegal = () => {
username.value = 123456
}
</script>
原理:
customRef是一个高阶工厂函数,把ref内部的track(收集依赖)、trigger(触发更新)两个核心能力暴露给开发者,让我们手动控制什么时候收集依赖、什么时候派发更新,不再由Vue内部自动处理。
TypeScript
开始调用 customRef(工厂函数)
↓
传入全局 track、trigger 给工厂函数
↓
执行工厂函数 → 得到 {get, set},闭包变量常驻
↓
返回 ref 实例(标记 __v_isRef = true)
------------------------------------------------------------------------------------------------
【读取 .value】
↓
执行 get()
↓
手动 track() → 收集当前effect依赖
↓
return 数据
------------------------------------------------------------------------------------------------
【修改 .value = newVal】
↓
执行 set(newVal)
↓
执行自定义业务逻辑(防抖/校验等等)
↓
满足条件手动 trigger()
↓
取出收集的所有 effect 执行 → 视图更新、watch触发
注意:
1 get里面必须调用track(),否则模板不会收集依赖,视图不更新
2 set修改完数据必须调用trigger(),否则不会通知视图刷新
3 不要在get/set里面写无限循环逻辑