Vue3 customRef 封神教程:防抖、本地存储、自动埋点一套搞定,模板干干净净

日常开发写搜索框防抖,几乎每个前端都写过,以前我们实现防抖只有两种路子:要么绑原生 input 事件,要么单独包个防抖函数。

先看看老写法有多难受,一眼就能看出毛病

html 复制代码
<script setup>
import { ref } from 'vue';
import { debounce } from 'lodash-es';
    
const keyword = ref('');
// 就为了做防抖,凭空多写一个单独函数
const handleInput = debounce((e) => {
  keyword.value = e.target.value;
  fetchData(keyword.value);
}, 500);
</script>
​
<template>
  <!-- 不能用顺手的v-model,只能手动绑定value和输入事件,代码割裂 -->
  <input :value="keyword" @input="handleInput" placeholder="请输入物料编码..." />
</template>

这段代码能正常跑,但看着特别别扭。Vue 本来靠 v-model 实现数据视图双向绑定,结果为了防抖,硬生生把数据流拆碎,模板和业务逻辑缠在一起,页面多了根本不好维护。

那有没有两全其美的办法?既能正常写v-model="keyword",不用手动绑事件,又能让变量自带防抖效果?

答案是 Vue3 自带的customRef,完美解决这个痛点。

customRef 到底是啥?

普通 ref 是 "一根筋",只要你一改值,立刻通知页面刷新,没有任何缓冲、拦截的机会。

customRef相当于给响应式变量装了个中控开关,给你两个核心控制权:

  1. track():收集依赖。代码 / 模板读取这个变量的时候调用,告诉 Vue:现在有地方在用我,后续我更新了要通知它。不写这个,变量变了页面不会刷新。
  2. trigger():触发更新。你觉得时机到了,调用它,Vue 才会更新页面、触发 watch 监听。

简单理解:普通 ref 自动执行 track+trigger,customRef 把这两步交给你手动控制,什么时候更新、要不要延迟更新、修改前加额外逻辑,全由你说了算。

封装防抖 Ref useDebouncedRef

我们封装一个通用钩子,创建出来的变量天生带防抖延迟,直接搭配 v-model 使用。

封装代码 composables/useDebouncedRef.ts

ts 复制代码
import { customRef } from 'vue';
​
// 泛型兼容所有数据类型,默认防抖500ms
export function useDebouncedRef<T>(initialValue: T, delay = 500) {
  let timeout: number | null = null;
​
  // customRef接收回调,入参track、trigger
  const refInstance = customRef((track, trigger) => {
    return {
      // 获取变量值时触发
      get() {
        track(); // 必须调用,收集依赖
        return initialValue;
      },
      // 给变量赋值时触发
      set(newValue: T) {
        // 每次输入先清空上一次定时器,实现防抖核心逻辑
        if (timeout) clearTimeout(timeout);
        timeout = window.setTimeout(() => {
          initialValue = newValue;
          trigger(); // 延迟结束才通知Vue更新
        }, delay);
      }
    };
  });
    
  // 组件卸载清空定时器,防止内存泄漏
  onUnmounted(() => {
    if (timeout) clearTimeout(timeout);
  });
  return refInstance;
}

页面使用示例

html 复制代码
<script setup lang="ts">
import { watch } from 'vue';
import { useDebouncedRef } from '@/composables/useDebouncedRef';
import { fetchLimsData } from '@/api';
​
// 生成自带500ms防抖的响应式变量
const keyword = useDebouncedRef('', 500);
​
// 只有停止输入500ms后,才会执行接口请求
watch(keyword, (newVal) => {
  fetchLimsData(newVal);
});
</script>
​
<template>
  <!-- 正常使用v-model,模板干净整洁,没有多余事件 -->
  <a-input v-model:value="keyword" placeholder="扫码或输入批次号..." />
</template>

补充容易踩的坑

  1. 定时器变量必须放在 customRef 外层,不能写在 get/set 内部,否则每次赋值都会新建定时器,防抖失效;
  2. 想要立即执行节流不能用这个,防抖是停止操作后延迟执行

自动埋点 Ref useTrackedRef

业务场景:表单开关、高危配置修改,产品要求只要改动就要记录操作日志,挨个给每个控件绑定 @change 太麻烦。

用 customRef 拦截赋值操作,修改值的同时自动上报埋点,不用改模板一行代码。

封装代码 composables/useTrackedRef.ts

ts 复制代码
import { customRef } from 'vue';
import { reportLog } from '@/utils/monitor';
​
// actionName:区分当前是哪个配置项
export function useTrackedRef<T>(initialValue: T, actionName: string) {
  return customRef((track, trigger) => {
    return {
      get() {
        track();
        return initialValue;
      },
      set(newValue: T) {
        // 只有新旧值不一样,才上报埋点,避免无意义重复上报
        if (initialValue !== newValue) {
          reportLog(`用户修改配置【${actionName}】,原值:${initialValue},新值:${newValue}`);
          initialValue = newValue;
          trigger();
        }
      }
    };
  });
}

页面使用

html 复制代码
<script setup lang="ts">
import { useTrackedRef } from '@/composables/useTrackedRef';
// 设备自动启动开关,修改自动埋点
const isAutoStart = useTrackedRef(false, '自动启动设备开关');
// 产线高危重启配置
const forceReboot = useTrackedRef(false, '产线强制重启开关');
</script>
​
<template>
  <a-switch v-model:checked="isAutoStart" />
  <a-switch v-model:checked="forceReboot" />
</template>

只要切换开关,不用加任何事件,后端自动收到操作审计日志。

持久化本地存储 Ref useLocalStorageRef

变量修改自动存入 localStorage,页面刷新优先读取缓存,支持对象 / 数组序列化。

封装代码 composables/useLocalStorageRef.ts

ts 复制代码
import { customRef } from 'vue';
​
export function useLocalStorageRef<T>(key: string, defaultValue: T) {
  // 初始化读取本地缓存
  let initVal: T;
  try {
    const cache = localStorage.getItem(key);
    initVal = cache ? JSON.parse(cache) : defaultValue;
  } catch (err) {
    // 解析失败、缓存损坏,使用默认值
    console.warn('本地缓存解析失败', err);
    initVal = defaultValue;
  }
​
  return customRef((track, trigger) => {
    return {
      get() {
        track();
        return initVal;
      },
      set(newVal: T) {
        initVal = newVal;
        trigger();
        try {
          // 同步存入本地存储
          localStorage.setItem(key, JSON.stringify(newVal));
        } catch (err) {
          console.error('本地存储写入失败', err);
        }
      }
    };
  });
}

使用示例

html 复制代码
<script setup lang="ts">
// 保存用户筛选条件,刷新页面不丢失
const searchFilter = useLocalStorageRef('material_search_filter', {
  code: '',
  status: 1
});
</script>
​
<template>
  <a-input v-model:value="searchFilter.code" />
  <a-select v-model:value="searchFilter.status" />
</template>

补充核心知识点

customRef 和普通 ref、shallowRef 的区别

  • ref:基础响应式,自动 track+trigger,无法拦截读写;
  • shallowRef:只监听第一层数据,深层属性变更不更新;
  • customRef:完全自定义读写逻辑,读写都能拦截,适合统一封装通用逻辑(防抖、缓存、埋点、权限校验)。

使用优势

  1. 逻辑内聚:所有延迟、缓存、埋点逻辑全部放在 hook 里,模板零污染;
  2. 复用性强:全项目多处搜索框、配置项,直接导入 hook 调用;
  3. 双向绑定不变:完美兼容 v-model,不用手动拆分 value 和事件;
  4. 数据驱动:把控制逻辑放在数据层,而不是视图事件层,符合 Vue 数据驱动思想。

适用场景汇总

  • 输入框防抖搜索(useDebouncedRef)
  • 表单配置自动操作埋点(useTrackedRef)
  • 数据本地持久化缓存(useLocalStorageRef)
  • 滚动、拖拽节流控制(useThrottleRef)
  • 赋值前做权限校验、数据格式化
  • 读取变量时自动加载配套字典数据

AI 快速生成钩子的万能 Prompt

text 复制代码
当前项目基于 Vue3 + TypeScript,采用<script setup>语法,帮我用 customRef 封装一个通用节流Hook:useThrottleRef。
需求说明:
1. 变量赋值走节流逻辑,固定间隔内只执行一次更新,避免频繁触发视图、接口;
2. 支持自定义节流间隔时间,默认300ms;
3. 泛型兼容所有基础类型、对象、数组,TS类型完整推导;
4. 组件卸载时清除定时器,杜绝内存泄漏;
5. 捕获异常,增加代码注释;
6. 附带一段完整业务组件使用示例;
7. get内部必须调用track(),set更新后必须调用trigger(),遵守customRef基础规范。

2026 年开发完全不用死记 customRef 写法,直接丢给 AI,3 秒生成可上线代码,大幅减少重复编码工作量。

普通前端和资深前端真正的差距,不在于你会多少花哨插件,而是能不能读懂框架底层到底是怎么跑的。

Vue3 特意把 track、trigger 这两个底层方法放出来,就是允许我们直接在响应式根源控制性能、拦截各种业务逻辑。不用再写一堆乱糟糟的拼接代码,换一套工程化思路统一管理数据流转,写出来的代码会干净好维护很多。

相关推荐
VOLUN1 小时前
TypeScript封装通用RESTful BaseAPI,后台接口代码精简80%
前端·javascript
胡永双1 小时前
Hexo + GitHub Pages搭建个人Blog教程(三)
前端
hunterandroid1 小时前
[Android 从零到一] 权限管理:运行时权限与最佳实践
前端
kyrie282 小时前
Redux 完整基础操作(原生 Redux,不结合 React-Redux)
前端
因_崔斯汀2 小时前
Vue 模板编译:HTML 是怎么变成 JS 的?
前端·vue.js
UXbot2 小时前
帮助企业低门槛开展AI应用开发的平台推荐
前端·低代码·ui·交互·产品经理·原型模式·web app
英勇无比的消炎药2 小时前
样式随心定制:TinyRobot 样式覆写与 CSS 变量实战解析
vue.js
橘子星2 小时前
基于 Vite 的多模态生图前端工程实践
前端·javascript·人工智能
想要成为糕糕手2 小时前
从零到一:CSS 3D 旋转立方体完全指南
前端·css·canvas