XCJ's Blog

XCJ

Vue3中的自定义ref

2025-09-03
Vue3中的自定义ref

什么是 customRef?

customRef 是一个工厂函数,接收一个工厂函数作为参数,并返回一个自定义的 Ref 对象。其类型定义如下:

function customRef<T>(factory: CustomRefFactory<T>): Ref<T>

type CustomRefFactory<T> = (
  track: () => void,
  trigger: () => void
) => {
  get: () => T
  set: (value: T) => void
}
  • track():在 get 方法中调用,用于追踪依赖。

  • trigger():在 set 方法中调用,用于通知更新。

  • factory 函数返回一个包含 getset 的对象,定义如何获取和设置值。

实现一个防抖 Ref

下面是一个 Vue 官方的 customRef 应用示例:创建一个防抖 ref,即只在最近一次 set 调用后的一段固定间隔后再调用:

import { customRef } from 'vue'

export 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)
      }
    }
  })
}

在组件中使用:

<script setup>
import { useDebouncedRef } from './debouncedRef'
const text = useDebouncedRef('hello')
</script>

<template>
  <input v-model="text" />
</template>

当用户在输入框中输入时,text 的值不会立即更新,而是在用户停止输入 200ms 后才更新并触发界面重新渲染。

其他应用场景

1. 同步到 localStorage

function useLocalStorageRef(key, defaultValue) {
  return customRef((track, trigger) => {
    let value = localStorage.getItem(key) ?? defaultValue;

    return {
      get() {
        track();
        return value;
      },
      set(newValue) {
        value = newValue;
        localStorage.setItem(key, newValue);
        trigger();
      }
    };
  });
}

2. 异步请求 Ref

function useAsyncRef(url) {
  let value = null;
  fetch(url).then(res => res.json()).then(data => {
    value = data;
    trigger();
  });

  return customRef((track, trigger) => ({
    get() {
      track();
      return value;
    },
    set(newValue) {
      value = newValue;
      trigger();
    }
  }));
}

总结

customRef 提供了对依赖追踪和更新触发的细粒度控制。

适用于防抖、节流、异步操作、状态持久化等场景。

谨慎使用

当使用 customRef 时,我们应该谨慎对待其 getter 的返回值,尤其是在每次运行 getter 时都生成新对象数据类型的情况下。当这样的 customRef 作为 prop 传递时,将影响父组件和子组件之间的关系。

父组件的渲染函数可能会被其他的响应式状态变化触发。在重新渲染过程中,我们会重新评估 customRef 的值,并返回一个新的对象数据类型作为子组件的 prop。这个 prop 会与其上一个值进行比较,由于两者不同,子组件中 customRef 的响应式依赖将被触发。与此同时,因为没有调用 customRef 的 setter,父组件中的响应式依赖不会运行。

官方文档连接: https://cn.vuejs.org/api/reactivity-advanced#customref