一、什么是 customRef?

  customRef 是 Vue3 提供的一个高级 API,它允许开发者创建自定义的响应式引用,并对其依赖追踪和更新触发进行精细化的逻辑控制。与普通的 ref 相比,customRef 赋予了我们对响应式过程更底层的控制能力。

二、基本语法和工作原理

customRef 接收一个工厂函数,该函数接受 track 和 trigger 两个回调函数作为参数,并返回一个包含 get 和 set 方法的对象:

import { customRef } from 'vue'

const myCustomRef = customRef((track, trigger) => {
  return {
    get() {
      // 读取值时调用
      track() // 依赖追踪:告诉Vue数据msg很重要,要对msg持续关注,一旦变化就更新
      return value
    },
    set(newValue) {
      // 设置值时调用
      value = newValue
      trigger() // 触发更新:通知Vue数据msg变化了
    }
  }
})

//如果他们两个其中省略一个会产生不一样的效果下面一个表格

总结表格

情况 track() trigger() 结果 适用场景
正常使用 完整的响应式 大多数情况
省略 track 数据变但界面不更新 几乎不用
省略 trigger 依赖追踪但无更新 手动控制更新
两者都省略 完全非响应式 不应该使用

三、实战:防抖 Ref 实现

让我们通过一个实际的防抖案例来理解 customRef 的强大之处:

// useDebouncedRef.ts
import { customRef } from 'vue'

export default function useDebouncedRef<T>(initialValue: T, delay = 200) {
  let timer: NodeJS.Timeout
  
  return customRef<T>((track, trigger) => {
    return {
      get() {
        track() // 声明依赖追踪
        return initialValue
      },
      set(value) {
        clearTimeout(timer)
        timer = setTimeout(() => {
          initialValue = value
          trigger() // 触发组件更新
        }, delay)
      }
    }
  })
}

在组件中使用:

vue

<template>
  <div>
    <input v-model="searchText" placeholder="搜索..." />
    <p>实时显示: {{ searchText }}</p>
  </div>
</template>

<script setup lang="ts">
import useDebouncedRef from './useDebouncedRef'

// 创建防抖响应式引用,延迟 500ms
const searchText = useDebouncedRef('', 500)
</script>

四、customRef vs 普通 ref:对比分析

1. 控制粒度不同

普通 ref:

const count = ref(0)
// 更新立即触发响应式
count.value++ // 立即更新组件
customRef:


const debouncedCount = useDebouncedRef(0, 1000)
// 更新可延迟触发
debouncedCount.value++ // 1秒后才更新组件

2. 适用场景对比

场景 普通 ref customRef
简单状态管理 完美适用 过于复杂
防抖/节流 需要额外代码 内置实现
异步更新控制 有限制 完全控制
复杂数据转换 需要计算属性 直接处理

更多实用场景

场景一:缓存数据请求

// useCachedRef.ts
export function useCachedRef<T>(key: string, initialValue: T, ttl = 300000) {
  return customRef<T>((track, trigger) => {
    // 尝试从 localStorage 读取缓存
    const cached = localStorage.getItem(key)
    let value = initialValue
    
    if (cached) {
      const { data, timestamp } = JSON.parse(cached)
      if (Date.now() - timestamp < ttl) {
        value = data
      }
    }
    
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        value = newValue
        // 保存到缓存
        localStorage.setItem(key, JSON.stringify({
          data: newValue,
          timestamp: Date.now()
        }))
        trigger()
      }
    }
  })
}

场景二:验证输入

// useValidatedRef.ts
export function useValidatedRef(
  initialValue: string, 
  validator: (value: string) => boolean
) {
  let value = initialValue
  let isValid = validator(initialValue)
  
  return customRef<string>((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        const wasValid = isValid
        value = newValue
        isValid = validator(newValue)
        
        // 只在值有效或有效性状态改变时触发更新
        if (isValid || wasValid !== isValid) {
          trigger()
        }
      }
    }
  })
}

最佳实践和注意事项

1. 内存管理

// 清理定时器防止内存泄漏
export function useAutoCleanupRef<T>(initialValue: T) {
  let timers: number[] = []
  
  const ref = customRef<T>((track, trigger) => {
    return {
      get() {
        track()
        return initialValue
      },
      set(value) {
        initialValue = value
        trigger()
      }
    }
  })
  
  // 提供清理方法
  const cleanup = () => {
    timers.forEach(timer => clearTimeout(timer))
    timers = []
  }
  
  return { ref, cleanup }
}

2. 错误处理

export function useSafeCustomRef<T>(initialValue: T) {
  return customRef<T>((track, trigger) => {
    return {
      get() {
        try {
          track()
          return initialValue
        } catch (error) {
          console.error('Error in customRef getter:', error)
          return initialValue
        }
      },
      set(value) {
        try {
          initialValue = value
          trigger()
        } catch (error) {
          console.error('Error in customRef setter:', error)
        }
      }
    }
  })
}

总结

customRef 是 Vue3 响应式系统中一个强大而灵活的工具,它为我们提供了:

  1. 精细控制:完全掌控依赖追踪和更新触发时机

  2. 逻辑封装:将复杂响应式逻辑封装为可复用函数

  3. 性能优化:实现防抖、节流、缓存等性能优化模式

  4. 业务解耦:将业务逻辑与组件代码分离

       如果面试的时候,hr问你customRef为什么会精细控制,其实要你的是trigger(触发更新)和track(依赖追踪) 很重要。

   虽然 customRef 提供了更大的灵活性,但也带来了更高的复杂性。在简单场景下,优先考虑使用普通的 ref 和 computed,只有在需要特殊控制逻辑时才选择 customRef

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐