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ù)使用,并且代碼精簡。
你可以在這里停一下,自己去嘗試嘗試,子辰在這里等你一會。
思考
我們先來看一個(gè)簡單的效果:在輸入框中輸入文本,下方會顯示我們輸入的文本。

<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ù)會隔一段時(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)芰α耍覀冋f過我們要做到極致的防抖!
那當(dāng)然要在最初的代碼模式上做防抖才算最完美。
首先讓我們觀察一下 const text = ref('') 得到了什么。我們在控制臺中輸出 text 看看。

通過輸出結(jié)果可以看出,text 得到了一個(gè)叫做 RefImpl 對象。
這個(gè)對象里有一個(gè) value 屬性,當(dāng)你看到 value 屬性是 (...) 時(shí),你應(yīng)該立刻明白它是通過Object.defineProperty 定義的,通過定義之后就會產(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 定義的
// 定義里會有一個(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.js在頁面加載時(shí)執(zhí)行某個(gè)方法
這篇文章主要介紹了詳解Vue.js在頁面加載時(shí)執(zhí)行某個(gè)方法的實(shí)現(xiàn)代碼,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-11-11
淺談VUE單頁應(yīng)用首屏加載速度優(yōu)化方案
這篇文章主要介紹了淺談VUE單頁應(yīng)用首屏加載速度優(yōu)化方案,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08
淺談vue中關(guān)于checkbox數(shù)據(jù)綁定v-model指令的個(gè)人理解
這篇文章主要介紹了淺談vue中關(guān)于checkbox數(shù)據(jù)綁定v-model指令的個(gè)人理解,v-model用于表單的數(shù)據(jù)綁定很常見,下面就來詳細(xì)的介紹一下2018-11-11
Vue數(shù)據(jù)更新視圖不更新的幾種解決方案小結(jié)
這篇文章主要介紹了Vue數(shù)據(jù)更新視圖不更新的幾種解決方案小結(jié),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
Vue在echarts?tooltip中添加點(diǎn)擊事件案例詳解
本文主要介紹了Vue項(xiàng)目中在echarts?tooltip添加點(diǎn)擊事件的案例詳解,代碼具有一定的價(jià)值,感興趣的小伙伴可以來學(xué)習(xí)一下2021-11-11

