Vue3 customRef自定义ref 实现防抖

本文介绍了如何在Vue中使用自定义ref实现防抖功能,通过自定义函数返回的对象包含get和set方法,配合track和trigger函数以及定时器,确保在用户输入完成后才更新数据,减轻服务器压力并实现响应式更新。

防抖就是防止在input 框中每输入一个字符就要向服务器请求一次,只要在用户输入完成过一段时间再读取用户输入的内容就能解决这个问题,减小服务器的压力。

1. 自定义ref是一个函数,可以接受参数。

比如我们自定义一个myRef:

setup() {
  let text = myRef("初始化");

  // 配置自定义的myRef
  function myRef(value) {}

  return { text };
},

 

2. 在这个函数中,我们有一个返回值,这个返回值是customRef 函数。

在使用customRef 函数之前,我们需要先对其进行引入:

import { customRef } from "vue";

setup() {
  let text = myRef("初始化");

  function myRef(value) {
    return customRef()
  }

  return { text };
},

 

3. customRef 函数的参数是一个函数。

该函数中又返回一个对象。

对象中有get函数和set函数。

setup() {
  let text = myRef("初始化");

  function myRef(value) {
    return customRef(() => {
      return {
        get() { },
        set() { }
      }
    })
  }

  return { text };
},

 

4. 当我们在模板中读取使用自定义ref 定义的数据时就会调用get 函数,修改数据时就会调用set 函数。原理和Proxy 对象中的get、set 差不多。

我们将get 函数中的返回值作为解析模板后数据的值。

而set 函数中,参数newValue为value修改后的值,我们需要将新的值赋值给传入的value值,才能实现页面的响应式。

setup() {
  let text = myRef("初始化");

  function myRef(value) {
    return customRef(() => {
      return {
        // 读取value 调用get
        get() {
          return value;
        },
        // 修改value 调用set
        set(newValue) {
          value = newValue;
        }
      }
    })
  }

  return { text };
},

5. 以上代码实现后,其实并没有真正实现响应式。至少说我们改变text数据,页面其他用到text的地方并没有改变。

原因就在于我们改变value值后触发get 函数后并没有去重新解析模板,set 也没有收到解析模板的命令。

因此customRef 函数中又有两个参数,track 函数和trigger 函数。

track函数用于通知vue追踪value的变化;trigger函数用于通知vue重新解析模板。

setup() {
  let text = myRef("初始化");

  function myRef(value) {
    return customRef((track, trigger) => {
      return {
        get() {
          track();  // 通知vue追踪value的变化
          return value;
        },
        set(newValue) {
          value = newValue;
          trigger();  // 通知vue重新解析模板
        }
      }
    })
  }

  return { text };
},

6. 最后我们加上定时器,就能实现防抖的效果。

setup() {
  let text = myRef("初始化", 1000);

  function myRef(value, delay) {
    let timer;  // 定时器
    return customRef((track, trigger) => {
      return {
        get() {
          track();
          return value;
        },
        set(newValue) {
          // 如果定时器已经开启了,就清除当前定时器,新开一个定时器
          clearTimeout(timer);  
          timer = setTimeout(() => {
            value = newValue;
            trigger();
          }, delay);
        },
      };
    });
  }

  return { text };
},
<think>我们参考了站内引用中提到的三种方法,并结合用户需求(在Vue3中使用JavaScript实现防抖功能)来提供解决方案。用户希望使用纯JavaScript实现,而不是依赖lodash库。下面将详细介绍三种方法,并给出最佳实践建议。 ### 方法一:自定义防抖函数(推荐) 这是最直接的方法,我们创建一个防抖函数,然后在Vue组件中使用它。 **步骤**: 1. 创建一个防抖函数工具(utils/debounce.js) 2.Vue组件中引入并使用 ```javascript // utils/debounce.js export function debounce(fn, delay = 500) { let timer = null; return function (...args) { clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); }, delay); }; } ``` 在Vue组件中使用: ```vue <template> <input type="text" @input="handleInput" /> </template> <script> import { debounce } from &#39;@/utils/debounce&#39;; export default { setup() { const handleInput = debounce((event) => { console.log(&#39;防抖处理:&#39;, event.target.value); // 这里执行实际业务逻辑,比如API请求 }, 800); return { handleInput }; }, }; </script> ``` **注意**:在Vue3的Composition API中,我们也可以直接在`<script setup>`中使用: ```vue <script setup> import { debounce } from &#39;@/utils/debounce&#39;; const handleInput = debounce((event) => { console.log(event.target.value); }, 800); </script> ``` ### 方法二:使用Vue3的`customRef`实现防抖(响应式数据防抖) 这种方法利用Vue3的`customRef`创建一个具有防抖特性的响应式数据。当数据变化时,防抖逻辑会延迟更新。 **代码实现**: ```vue <template> <input v-model="keyword" /> <h3>{{ keyword }}</h3> </template> <script> import { customRef } from &#39;vue&#39;; export default { setup() { // 自定义防抖ref function debounceRef(value, delay = 500) { let timer; return customRef((track, trigger) => { return { get() { track(); // 依赖追踪 return value; }, set(newValue) { clearTimeout(timer); timer = setTimeout(() => { value = newValue; trigger(); // 触发更新 }, delay); }, }; }); } const keyword = debounceRef(&#39;&#39;, 500); return { keyword }; }, }; </script> ``` 在`<script setup>`中的写法: ```vue <script setup> import { customRef } from &#39;vue&#39;; const keyword = debounceRef(&#39;&#39;); function debounceRef(value, delay = 500) { let timer; return customRef((track, trigger) => { return { get() { track(); return value; }, set(newValue) { clearTimeout(timer); timer = setTimeout(() => { value = newValue; trigger(); }, delay); } }; }); } </script> ``` ### 方法三:在`watchEffect`或`watch`中使用防抖 如果你需要对一个响应式数据的变化进行防抖处理,可以在watchEffect或watch中使用防抖函数。 ```vue <script setup> import { ref, watch } from &#39;vue&#39;; import { debounce } from &#39;@/utils/debounce&#39;; const searchTerm = ref(&#39;&#39;); // 使用防抖函数包装回调 const debouncedSearch = debounce((value) => { console.log(&#39;执行搜索:&#39;, value); // 实际业务逻辑 }, 600); // 监听searchTerm的变化,并应用防抖 watch(searchTerm, (newValue) => { debouncedSearch(newValue); }); </script> ``` ### 最佳实践与注意事项 1. **清除定时器**:在组件卸载时,清除防抖函数中的定时器,避免内存泄漏。 ```javascript // 在自定义防抖函数中,我们可以返回一个带有cancel方法的函数 export function debounce(fn, delay) { let timer = null; function debounced(...args) { clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); }, delay); } debounced.cancel = () => { clearTimeout(timer); }; return debounced; } // 在组件中使用 import { onUnmounted } from &#39;vue&#39;; const debouncedFn = debounce(/* ... */); onUnmounted(() => { debouncedFn.cancel(); }); ``` 2. **this指向**:在自定义防抖函数中,我们使用普通函数(而不是箭头函数)来确保正确的`this`指向。在Vue3的setup中,由于箭头函数会绑定外层this(通常是undefined),所以使用普通函数并利用闭包传递this是安全的。 3. **参数传递**:使用`...args`来传递所有参数,确保事件对象或其他参数能够正确传递。 ### 总结 - 对于事件处理函数(如`@input`),使用方法一(自定义防抖函数)最简单直接。 - 对于响应式数据(如`v-model`绑定的数据),使用方法二(`customRef`)可以创建具有防抖特性的ref。 - 如果需要对一个响应式数据的变化进行防抖监听,使用方法三(`watch`+防抖函数)更合适。 根据你的具体场景选择合适的方法即可。 §§相关问题§§ 1.Vue3中,如何实现防抖和节流的混合控制(例如:立即执行一次,然后防抖)? 2. 如何将防抖逻辑抽象为Vue3的Composition API可复用Hook? 3.Vue3的`<script setup>`中,如何正确绑定防抖函数的this上下文? 4. 使用`customRef`实现防抖时,如何确保组件卸载时清除定时器? 5. 防抖函数在Vue3中处理异步请求时,如何避免竞态条件?</think>在 Vue 3 中使用 JavaScript 实现防抖功能有以下几种高效方法,我将结合最佳实践为您详细说明: ### 方法一:自定义防抖函数(推荐) ```javascript // utils/debounce.js export function debounce(fn, delay = 500) { let timer = null; return function (...args) { const context = this; clearTimeout(timer); timer = setTimeout(() => { fn.apply(context, args); }, delay); }; } ``` 在组件中使用: ```vue <template> <input @input="handleInput" /> </template> <script> import { debounce } from &#39;@/utils/debounce&#39;; export default { setup() { const handleInput = debounce((event) => { console.log(&#39;防抖值:&#39;, event.target.value); // 执行实际业务逻辑 }, 800); return { handleInput }; } }; </script> ``` ### 方法二:使用 Vue 3customRef(响应式防抖) ```vue <template> <input v-model="keyword" /> <p>防抖后的值: {{ debouncedValue }}</p> </template> <script> import { customRef } from &#39;vue&#39;; export default { setup() { // 创建防抖ref function debounceRef(value, delay = 500) { let timer; return customRef((track, trigger) => { return { get() { track(); // 依赖追踪 return value; }, set(newValue) { clearTimeout(timer); timer = setTimeout(() => { value = newValue; trigger(); // 触发更新 }, delay); } }; }); } const keyword = debounceRef(&#39;&#39;, 600); return { keyword }; } }; </script> ``` ### 方法三:Composition API 防抖 Hook(可复用) ```javascript // hooks/useDebounce.js import { customRef } from &#39;vue&#39;; export function useDebounce(initialValue, delay = 500) { return debounceRef(initialValue, delay); function debounceRef(value, delay) { let timer; return customRef((track, trigger) => ({ get() { track(); return value; }, set(newValue) { clearTimeout(timer); timer = setTimeout(() => { value = newValue; trigger(); }, delay); } })); } } ``` 在组件中使用: ```vue <script setup> import { useDebounce } from &#39;@/hooks/useDebounce&#39;; const searchQuery = useDebounce(&#39;&#39;, 700); // 监听防抖值变化 watch(() => searchQuery.value, (newVal) => { console.log(&#39;最终搜索值:&#39;, newVal); // 执行搜索逻辑 }); </script> ``` ### 最佳实践建议 1. **组件卸载时清除定时器** ```javascript import { onUnmounted } from &#39;vue&#39;; // 在自定义防抖函数中 export function debounce(fn, delay) { let timer = null; const cancel = () => clearTimeout(timer); const debounced = (...args) => { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); }; debounced.cancel = cancel; return debounced; } // 在组件中使用 setup() { const searchFn = debounce(/*...*/, 500); onUnmounted(() => { searchFn.cancel(); }); } ``` 2. **参数传递技巧** ```javascript // 支持事件对象和自定义参数 const handleSearch = debounce((event, customParam) => { console.log(event.target.value, customParam); }, 600); // 模板中使用 <input @input="(e) => handleSearch(e, &#39;custom&#39;)"> ``` 3. **立即执行选项** ```javascript function debounce(fn, delay, immediate = false) { let timer; return function(...args) { const context = this; const later = () => { timer = null; if (!immediate) fn.apply(context, args); }; const callNow = immediate && !timer; clearTimeout(timer); timer = setTimeout(later, delay); if (callNow) fn.apply(context, args); }; } ``` ### 应用场景示例 ```vue <template> <!-- 搜索框防抖 --> <input v-model="searchInput" placeholder="搜索..."> <!-- 按钮防抖 --> <button @click="debouncedSubmit">提交</button> <!-- 窗口大小变化防抖 --> <div :style="{ width: windowWidth + &#39;px&#39; }">窗口宽度: {{ windowWidth }}</div> </template> <script setup> import { ref, onMounted, onUnmounted } from &#39;vue&#39;; import { debounce } from &#39;@/utils/debounce&#39;; const searchInput = ref(&#39;&#39;); const windowWidth = ref(window.innerWidth); // 搜索防抖 const handleSearch = debounce((value) => { console.log(&#39;执行搜索:&#39;, value); }, 500); // 提交防抖 const debouncedSubmit = debounce(() => { console.log(&#39;表单提交&#39;); }, 1000, true); // 立即执行 // 窗口大小防抖 const handleResize = debounce(() => { windowWidth.value = window.innerWidth; }, 300); onMounted(() => { window.addEventListener(&#39;resize&#39;, handleResize); }); onUnmounted(() => { window.removeEventListener(&#39;resize&#39;, handleResize); handleResize.cancel(); }); </script> ``` ### 关键注意事项 1. **保持防抖函数单例**:确保防抖函数在组件生命周期内只创建一次 2. **正确绑定 this**:使用普通函数而非箭头函数确保正确上下文 3. **内存管理**:组件卸载时务必清除定时器 4. **参数传递**:使用 `...args` 确保所有参数正确传递 5. **响应式更新**:使用 `customRef` 可直接创建防抖响应式变量 > 对于搜索框等高频操作,建议设置 300-800ms 的防抖延迟[^1]。按钮防抖可设置更短的 100-300ms 延迟[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值