Vue3中customRef详解

📅 2026/7/17 16:59:29
Vue3中customRef详解
Vue3中customRef详解1. 核心概念与工作原理2. 为什么需要 customRef3. 常见应用场景与代码示例场景一实现防抖 Ref最经典用例场景二实现带验证的 Ref场景三LocalStorage 同步 Ref场景四异步 Ref4. customRef 与普通 ref 的对比5. 注意事项与最佳实践customRef是 Vue 3 提供的一个高级响应式 API它允许开发者创建一个自定义的ref对象并完全控制其依赖追踪Dependency Tracking和触发更新Triggering Updates的行为。相较于普通的refcustomRef提供了更细粒度的控制权适用于防抖、节流、异步数据获取、本地存储同步等复杂场景。1. 核心概念与工作原理基本结构customRef接收一个工厂函数作为参数。该工厂函数接收两个关键参数track和trigger并必须返回一个包含get和set方法的对象。import{customRef}fromvueconstmyCustomRefcustomRef((track,trigger){letvalueinitial value// 内部状态通常通过闭包保存return{get(){// 1. 告诉 Vue 当前副作用如组件渲染依赖了这个值track()returnvalue},set(newValue){// 2. 更新内部值valuenewValue// 3. 告诉 Vue 值已改变需要重新运行依赖该值的副作用trigger()}}})关键函数说明track()在get方法中调用。用于通知 Vue 的响应式系统“当前正在读取这个值请收集依赖”。如果不调用track()即使值改变了视图也不会更新。trigger()在set方法中调用。用于通知 Vue 的响应式系统“这个值已经改变请通知所有依赖它的地方进行更新”。如果不调用trigger()修改值后视图不会重新渲染。2. 为什么需要customRef普通的ref或reactive在赋值后会立即触发视图更新。但在某些业务场景下我们需要干预这个过程防抖Debounce用户输入搜索关键词时不希望每输入一个字符就发起一次请求而是希望停止输入一段时间后再接口请求。节流Throttle限制函数调用的频率。异步处理在设置值时执行异步操作如 API 请求并在完成后才更新视图。数据验证在设置值之前进行校验只有合法的值才更新内部状态。外部状态同步将响应式数据与localStorage、sessionStorage或 Web Worker 保持同步。3. 常见应用场景与代码示例场景一实现防抖 Ref最经典用例这是customRef最常见的使用场景用于优化搜索框等高频输入场景。import{customRef}fromvue/** * 创建一个防抖的 ref * param {*} initialValue 初始值 * param {number} delay 延迟时间毫秒 */functionuseDebouncedRef(initialValue,delay500){lettimeoutIdletvalueinitialValuereturncustomRef((track,trigger){return{get(){track()// 收集依赖returnvalue},set(newValue){// 每次设置新值时清除之前的定时器clearTimeout(timeoutId)// 设置新的定时器延迟更新值和触发视图更新timeoutIdsetTimeout((){valuenewValuetrigger()// 触发更新},delay)}}})}// 在组件中使用// const searchText useDebouncedRef(, 500)// input v-modelsearchText /// 视图只有在停止输入 500ms 后才会更新场景二实现带验证的 Ref在设置值之前进行校验如果值不合法可以拒绝更新或记录错误信息。import{customRef}fromvuefunctionuseValidatedRef(initialValue,validator){letvalueinitialValueleterrornullreturncustomRef((track,trigger){return{get(){track()return{value,error}// 返回包含值和错误信息的对象},set(newValue){constisValidvalidator(newValue)if(isValidtrue){valuenewValue errornull}else{errorisValid// 假设 validator 返回错误消息字符串// 注意这里可以选择是否 trigger()。// 如果希望视图显示错误信息需要 trigger()// 如果希望非法值不更新视图中的 value则只更新 error 并 trigger}trigger()}}})}// 使用示例// const ageRef useValidatedRef(18, (val) {// if (val 0 val 120) return true// return 年龄必须在 0-120 之间// })场景三LocalStorage 同步 Ref实现响应式数据与浏览器本地存储的双向同步。import{customRef}fromvuefunctionuseLocalStorageRef(key,initialValue){// 从 localStorage 读取初始值如果没有则使用默认值conststoredValuelocalStorage.getItem(key)letvaluestoredValue?JSON.parse(storedValue):initialValuereturncustomRef((track,trigger){return{get(){track()returnvalue},set(newValue){valuenewValue// 同步写入 localStoragelocalStorage.setItem(key,JSON.stringify(newValue))trigger()}}})}// 使用示例// const theme useLocalStorageRef(theme, light)// 修改 theme.value 会自动更新 localStorage场景四异步 Ref处理异步数据获取例如在设置值时触发 API 请求。import{customRef}fromvuefunctionuseAsyncRef(asyncFetcher,initialValuenull){letvalueinitialValueletloadingfalseleterrornullreturncustomRef((track,trigger){return{get(){track()return{value,loading,error}},asyncset(params){loadingtrueerrornulltrigger()// 触发加载状态更新try{constresultawaitasyncFetcher(params)valueresult}catch(e){errore}finally{loadingfalsetrigger()// 触发最终结果更新}}}})}4.customRef与普通ref的对比特性ref()customRef()依赖追踪自动手动调用track()触发更新自动手动调用trigger()灵活性低仅基本响应式高可插入任意逻辑适用场景大多数常规状态管理防抖、节流、异步、验证、外部同步代码复杂度简单较复杂需维护内部状态5. 注意事项与最佳实践必须调用track和trigger如果在get中忘记调用track()Vue 无法知道组件依赖了这个值导致值变化时视图不更新。如果在set中忘记调用trigger()修改值后视图不会重新渲染。避免无限循环不要在get或set中直接访问其他响应式对象的.value并立即触发更新这可能导致死循环。如果需要在set中更新其他ref请确保逻辑清晰避免相互触发。闭包保存状态customRef内部的value变量通常通过闭包保存。不要直接暴露这个变量而是通过get/set访问以确保响应式系统的正常工作。性能考虑customRef提供了强大的控制力但也带来了额外的代码复杂度。只有在普通ref、computed或watch无法满足需求时才建议使用customRef。与v-model兼容customRef返回的对象与普通ref一样可以直接用于v-model绑定这使得它在表单处理中非常有用如防抖输入框。总结customRef是 Vue 3 响应式系统中的一个强大工具它打破了默认响应式行为的限制让开发者能够精确控制数据的读取和更新时机。通过合理使用track和trigger可以实现防抖、节流、异步处理、数据验证等高级功能从而提升应用的性能和用户体验。在实际开发中建议将常用的customRef逻辑封装成 Composable 函数Hooks以便在项目中复用。