Vue3中customRef自定义ref过程
作者:Rysxt
一、customRef 基础概念
customRef 是 Vue3 提供的高级响应式 API,用于创建自定义行为的 ref 对象。
与普通 ref(ref()
)的固定 getter/setter 逻辑不同,customRef 允许开发者完全控制依赖追踪和更新触发,适用于防抖、节流、异步更新、数据验证等复杂场景。
其核心原理是通过工厂函数返回包含 get
和 set
方法的对象:
get()
:读取数据时触发,需调用track()
告诉 Vue“这个值被依赖”;set(newValue)
:修改数据时触发,需调用trigger()
告诉 Vue“这个值更新了”。
二、customRef 基本用法
1. 创建简单 customRef
以下代码实现一个基础 customRef,功能与普通 ref 类型,但能更清晰地看到依赖追踪和更新触发的流程:
import { customRef } from 'vue'; // 工厂函数:接收初始值,返回 customRef 对象 function useBasicRef(initialValue) { let value = initialValue; // 闭包保存值 return customRef((track, trigger) => { return { // 读取值时触发,调用 track() 收集依赖 get() { track(); // 告诉 Vue 这个值被依赖(如模板中的 {{ value }}) console.log('获取值:', value); return value; }, // 修改值时触发,调用 trigger() 通知更新 set(newValue) { value = newValue; // 更新值 console.log('设置值:', newValue); trigger(); // 通知 Vue 重新渲染依赖该值的组件 } }; }); } // 在组件中使用 const basicRef = useBasicRef('初始值');
2. 与普通 ref 的区别
特性 | 普通 ref (ref()) | customRef (customRef()) |
---|---|---|
逻辑控制 | 固定 getter/setter(仅实现基本响应式) | 完全自定义(可插入验证、防抖等逻辑) |
灵活性 | 低 | 高(满足复杂场景需求) |
适用场景 | 基本响应式需求(如存储用户输入) | 需要特殊行为的场景(如延迟更新) |
三、常见应用场景与实现
1. 防抖 ref(延迟更新)
场景:用户输入时,避免频繁触发视图更新(如搜索框输入),仅在停止输入后更新。
import { customRef } from 'vue'; function useDebouncedRef(initialValue, delay = 500) { let timer = null; // 闭包保存定时器 let value = initialValue; return customRef((track, trigger) => { return { get() { track(); // 收集依赖 return value; }, set(newValue) { clearTimeout(timer); // 清除之前的定时器 timer = setTimeout(() => { value = newValue; // 延迟后更新值 trigger(); // 触发更新 }, delay); } }; }); } // 在组件中使用 const searchQuery = useDebouncedRef('', 500); // 模板中:<input v-model="searchQuery" />
效果:用户输入时,视图不会立即更新,仅在停止输入 500ms 后更新。
2. 数据验证 ref
场景:输入数据时,实时验证合法性(如邮箱格式、年龄范围),若不合法则提示错误。
import { customRef } from 'vue'; function useValidatedRef(initialValue, validator) { let value = initialValue; let error = null; return customRef((track, trigger) => { return { get() { track(); // 收集依赖 return { value, error }; // 返回值和错误信息 }, set(newValue) { const validationResult = validator(newValue); // 验证数据 if (validationResult === true) { value = newValue; // 验证通过,更新值 error = null; } else { error = validationResult; // 验证失败,保存错误信息 } trigger(); // 触发更新(视图会显示错误信息) } }; }); } // 验证函数:检查年龄是否在 18-120 之间 const ageValidator = (value) => { if (typeof value !== 'number' || value < 18 || value > 120) { return '年龄必须在 18-120 之间'; } return true; }; // 在组件中使用 const age = useValidatedRef(20, ageValidator); // 模板中:<input v-model.number="age.value" /><span v-if="age.error" style="color: red">{{ age.error }}</span>
效果:输入非法年龄(如 10)时,会显示错误提示。
3. 异步 ref(异步更新)
场景:从服务器获取数据后,更新 ref 的值(如用户信息加载)。
import { customRef } from 'vue'; function useAsyncRef(asyncFunction) { let value = null; let isLoading = true; return customRef((track, trigger) => { return { get() { track(); // 收集依赖 return { value, isLoading }; // 返回值和加载状态 }, set(newValue) { // 触发异步操作(如获取用户信息) asyncFunction(newValue).then((data) => { value = data; // 更新值 isLoading = false; // 结束加载 trigger(); // 触发更新 }).catch((error) => { console.error('异步操作失败:', error); isLoading = false; // 结束加载 trigger(); // 触发更新 }); } }; }); } // 模拟异步函数:获取用户信息 const fetchUserInfo = (userId) => { return new Promise((resolve) => { setTimeout(() => { resolve({ id: userId, name: '张三' }); }, 1000); }); }; // 在组件中使用 const userInfo = useAsyncRef(fetchUserInfo); // 模板中:<div v-if="userInfo.isLoading">加载中...</div><div v-else>{{ userInfo.value.name }}</div>
效果:设置 userInfo.value
为 userId 后,会显示“加载中...”,1 秒后显示用户姓名。
四、best 实践
1. 封装为组合式函数
将 customRef 逻辑封装为独立的组合式函数(如 useDebouncedRef
、useValidatedRef
),提高代码复用性。避免在组件中重复编写 get/set 逻辑。
2. 正确处理依赖
在 get
方法中必须调用 track()
,否则 Vue 无法追踪依赖,导致视图不更新;在 set
方法中必须调用 trigger()
,否则视图不会重新渲染。
3. 避免内存泄漏
若 customRef 中使用了定时器、事件监听器等资源,需在组件卸载时清理(如 onUnmounted
钩子中清除定时器),防止内存泄漏。
4. 性能优化
- 避免在
get
方法中执行昂贵计算(如复杂循环),可将结果缓存; - 在
set
方法中,可通过比较新旧值(如if (newValue !== value)
)避免不必要的更新。
通过以上教程,你可以掌握 Vue3 中 customRef 的基本用法和常见场景,灵活实现自定义响应式逻辑。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。