vue3利用customRef實(shí)現(xiàn)防抖
防抖你一定學(xué)過,但是你知道如何在 Vue3 里中將防抖做到極致嗎?什么樣的防抖才算是極致呢?
大家好,我是渡一前端子辰老師,首先,讓我們回顧一下防抖的概念。
防抖就是對于頻繁觸發(fā)的事件添加一個(gè)延時(shí)同時(shí)設(shè)定一個(gè)最小觸發(fā)間隔,如果觸發(fā)間隔小于設(shè)定的間隔,則清除原來的定時(shí),重新設(shè)定新的定時(shí);如果觸發(fā)間隔大于設(shè)定間隔,則保留原來的定時(shí),并設(shè)置新的定時(shí);
防抖的結(jié)果就是頻繁的觸發(fā)轉(zhuǎn)變?yōu)橛|發(fā)一次。
接下來,讓我們看一個(gè)具體的業(yè)務(wù)需求。
在項(xiàng)目中,我們有一個(gè)輸入框,我們希望在用戶輸入后間隔一段時(shí)間再執(zhí)行指定的邏輯。同時(shí),由于防抖需要在項(xiàng)目中多處使用,因此我們希望能夠盡可能方便地重復(fù)使用,并且代碼精簡。
你可以在這里停一下,自己去嘗試嘗試,子辰在這里等你一會(huì)。
思考
我們先來看一個(gè)簡單的效果:在輸入框中輸入文本,下方會(huì)顯示我們輸入的文本。

<template>
<div class="container">
<input v-model="text" />
<p class="result">{{ text }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue';
const text = ref('');
</script>
然后我們現(xiàn)在要加入防抖功能,這意味著數(shù)據(jù)的變化不能立即發(fā)生,而是要等待一小段時(shí)間后再發(fā)生變化。
因此不能再使用 v-model,因?yàn)?v-model 的雙向綁定是數(shù)據(jù)及時(shí)響應(yīng)變化的。
不過我們可以利用 v-model 的特性,將其拆分為 :value="" 和 @input="" 的形式。
<template>
<div class="container">
<!-- @input 綁定一個(gè) handleUpdate 函數(shù) -->
<input :value="text" @input="handleUpdate" />
<p class="result">{{ text }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue';
const text = ref('');
const handleUpdate = (e) => {
text.value = e.target.value
}
</script>
這種形式就是 v-model 的原始形式,效果同 v-model 是一致的。
改成這種寫法之后,我們就可以利用 handleUpdate 函數(shù)進(jìn)行操作了。
因?yàn)檫@里的防抖無非就是讓數(shù)據(jù)的變化延遲執(zhí)行,所以我們可以這樣寫:
<script setup>
import { ref } from 'vue';
const text = ref('');
// 我們在這里定義一個(gè) timer
let timer;
const handleUpdate = (e) => {
// 每一次執(zhí)行這個(gè)函數(shù)的時(shí)候我們將 timer 情況
clearTimeout(timer)
// 然后再執(zhí)行 setTimeout,比如說間隔 1000ms
setTimeout(() => {
text.value = e.target.value
}, 1000)
}
</script>

/**
* @description: 防抖函數(shù)
* @param {Function} func: 要延遲執(zhí)行的函數(shù)
* @param {Number} delay: 延遲時(shí)間 ( 默認(rèn) 1000ms )
*/
export function debounce(func, delay = 1000) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
func.call(this, ...args);
}, delay);
};
}
這樣我們就封裝好了一個(gè)防抖函數(shù)。
現(xiàn)在讓我們在組件中使用它。
<script setup>
import { ref } from 'vue';
import { debounce } from './debounce';
const text = ref('');
const update = (e) => (text.value = e.target.value)
// 將 update 和 延遲時(shí)間傳入進(jìn)去
// 當(dāng)數(shù)據(jù)變化的時(shí)候,運(yùn)行的是防抖函數(shù)
// 那么防抖函數(shù)會(huì)隔一段時(shí)間再去運(yùn)行這個(gè) update 函數(shù),去更新數(shù)據(jù)
const handleUpdate = debounce(update, 1000)
</script>

現(xiàn)在運(yùn)行一下,效果還是沒問題的。
但是現(xiàn)在看起來還是不夠舒服,而且 v-model 原本用得好好地卻被換成了 :value="" 和 @input="" 的形式。
而且每次使用時(shí)還需要寫很多冗余代碼,這我可忍受不了。
但是要繼續(xù)優(yōu)化就需要考驗(yàn)?zāi)芰α?,我們說過我們要做到極致的防抖!
那當(dāng)然要在最初的代碼模式上做防抖才算最完美。
首先讓我們觀察一下 const text = ref('') 得到了什么。我們在控制臺中輸出 text 看看。

通過輸出結(jié)果可以看出,text 得到了一個(gè)叫做 RefImpl 對象。
這個(gè)對象里有一個(gè) value 屬性,當(dāng)你看到 value 屬性是 (...) 時(shí),你應(yīng)該立刻明白它是通過Object.defineProperty 定義的,通過定義之后就會(huì)產(chǎn)生 get 和 set 兩個(gè)函數(shù)。
也就是說,ref 的源代碼看上去就像是 get 和 set 的結(jié)構(gòu)。
根據(jù)上面發(fā)現(xiàn),讓我們寫出 ref 方法的結(jié)構(gòu):
// ref 方法需要一個(gè)值,我們寫作 value
function ref(value) {
// 它返回一個(gè)對象
{
// 對象中有一個(gè) value 屬性
// 是通過 Object.defineProperty 定義的
// 定義里會(huì)有一個(gè) get 和 set 方法
get(){
// get 方法返回 value
return value
},
set(val){
// set 方法給 value重新賦值
value = val
}
}
}
// 大概就是這個(gè)樣子
// 但是不要忘記了 vue 是帶有響應(yīng)式的
// 而響應(yīng)式的原理是什么呢?
// 就是在 get 的時(shí)候進(jìn)行依賴收集,就是說誰用到了我這個(gè)數(shù)據(jù)
// 而 set 的時(shí)候叫做派發(fā)更新,表示通知使用數(shù)據(jù)的地方數(shù)據(jù)更新了
// 那么就應(yīng)該是如下的樣子
function ref(value) {
{
get(){
// 依賴收集,我們稱為 track()
return value
},
set(val){
// 派發(fā)更新 trigger()
value = val
}
}
}
那么這跟我們要做的防抖有什么關(guān)系呢?
再看一下:如果能夠延遲 set 的派發(fā)更新,也就是說將 set 放到一個(gè)防抖函數(shù)中延遲執(zhí)行,那么問題就解決了。
那是否意味著我們要重新寫一個(gè) ref 呢?將其源代碼重新實(shí)現(xiàn)一遍?其實(shí)沒有必要。
因?yàn)?vue 提供了一個(gè)自定義 ref 入口:customRef。
通過 customRef 可以自定義 get 和 set 方法。
那么通過 customRef 我們就看到了勝利的曙光了,讓我們利用它寫出自己的 ref。
實(shí)現(xiàn)
import { customRef } from "vue";
/**
* @description: 防抖的 ref
* @param {*} value: ref 的 value
* @param {*} delay: 延遲時(shí)間,表示 value 的更新要在多少時(shí)間后觸發(fā) ( 默認(rèn)為 1000ms )
*/
export function debounceRef(value, delay = 1000) {
let timer;
// 我們這里利用 customRef 來自定義 ref
return customRef(() => {
// customRef 中返回一個(gè)對象,對象中包含 get 和 set 函數(shù)
return {
get() {
// 依賴收集 track()
return value;
},
set(val) {
// 使用 setTimeout 實(shí)現(xiàn)防抖
clearTimeout(timer);
timer = setTimeout(() => {
// 派發(fā)更新 trigger()
value = val;
}, delay);
},
};
});
}
上面代碼中我們實(shí)現(xiàn)了自定義 ref,并實(shí)現(xiàn)了防抖功能。
但缺少最關(guān)鍵依賴收集和派發(fā)更新部分,我們沒有時(shí)間重新實(shí)現(xiàn)依賴收集源碼和派發(fā)更新源碼。
但是 vue 貼心的考慮到了這一點(diǎn),它給我們傳入了兩個(gè)參數(shù),track 和 trigger,分別表示依賴收集和派發(fā)更新,我們只要在合適的時(shí)候調(diào)用即可。
import { customRef } from "vue";
export function debounceRef(value, delay = 1000) {
let timer;
return customRef((track, trigger) => {
// 獲得 track, trigger 函數(shù)
return {
get() {
// 依賴收集 track()
track();
return value;
},
set(val) {
clearTimeout(timer);
timer = setTimeout(() => {
value = val;
// 派發(fā)更新 trigger()
trigger();
}, delay);
},
};
});
}
現(xiàn)在去使用一下,看看效果。
<template>
<div class="container">
<input v-model="text" />
<p class="result">{{ text }}</p>
</div>
</template>
<script setup>
// 在使用時(shí),我們要引入自定義的 ref
import { debounceRef } from './debounceRef';
const text = debounceRef('');
</script>
我們輸出 text 到控制臺看看。

可以看出得到的是一個(gè) CustomRefImpl 對象,這個(gè) value 呢,也是一個(gè) get 和 set,只不過現(xiàn)在用的是我們自己寫的 get 和 set。
來試一下效果。

總結(jié)
通過現(xiàn)在的代碼,我們可以輕松實(shí)現(xiàn)防抖的功能,而且使用時(shí)非常簡單,只需要一個(gè) ref 就可以做到。
這種方式給我們帶來了很大的便利,讓我們在開發(fā)過程中更加高效。
以上就是vue3利用customRef實(shí)現(xiàn)防抖的詳細(xì)內(nèi)容,更多關(guān)于vue3 customRef防抖的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue開發(fā)chrome插件,實(shí)現(xiàn)獲取界面數(shù)據(jù)和保存到數(shù)據(jù)庫功能
這篇文章主要介紹了vue開發(fā)chrome插件,實(shí)現(xiàn)獲取界面數(shù)據(jù)和保存到數(shù)據(jù)庫功能的示例,幫助大家更好的理解和使用vue,感興趣的朋友可以了解下2020-12-12
Vue學(xué)習(xí)筆記進(jìn)階篇之vue-cli安裝及介紹
這篇文章主要介紹了Vue學(xué)習(xí)筆記進(jìn)階篇之vue-cli安裝及介紹,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
使用WebStorm運(yùn)行vue項(xiàng)目的詳細(xì)圖文教程
在WebStorm中怎么打開一個(gè)已有的項(xiàng)目,這個(gè)不用多說,那么如何運(yùn)行一個(gè)vue項(xiàng)目呢?下面這篇文章主要給大家介紹了關(guān)于使用WebStorm運(yùn)行vue項(xiàng)目的相關(guān)資料,需要的朋友可以參考下2023-02-02
vue項(xiàng)目引入遠(yuǎn)程jweixin-1.2.0.js文件并使用詳解
這篇文章主要介紹了vue項(xiàng)目引入遠(yuǎn)程jweixin-1.2.0.js文件并使用方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03
Vue使用vue-cli創(chuàng)建項(xiàng)目
這篇文章主要介紹了Vue使用vue-cli創(chuàng)建項(xiàng)目,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09
vue-router 控制路由權(quán)限的實(shí)現(xiàn)
這篇文章主要介紹了vue-router 控制路由權(quán)限的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
vue3?element?plus按需引入最優(yōu)雅的用法實(shí)例
這篇文章主要給大家介紹了關(guān)于vue3?element?plus按需引入最優(yōu)雅的用法,以及關(guān)于Element-plus按需引入的一些坑,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-03-03
Vue-cli3 $ is not defined錯(cuò)誤問題及解決
這篇文章主要介紹了Vue-cli3 $ is not defined錯(cuò)誤問題及解決,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07

