vue3利用customRef實現(xiàn)防抖
防抖你一定學(xué)過,但是你知道如何在 Vue3 里中將防抖做到極致嗎?什么樣的防抖才算是極致呢?
大家好,我是渡一前端子辰老師,首先,讓我們回顧一下防抖的概念。
防抖就是對于頻繁觸發(fā)的事件添加一個延時同時設(shè)定一個最小觸發(fā)間隔,如果觸發(fā)間隔小于設(shè)定的間隔,則清除原來的定時,重新設(shè)定新的定時;如果觸發(fā)間隔大于設(shè)定間隔,則保留原來的定時,并設(shè)置新的定時;
防抖的結(jié)果就是頻繁的觸發(fā)轉(zhuǎn)變?yōu)橛|發(fā)一次。
接下來,讓我們看一個具體的業(yè)務(wù)需求。
在項目中,我們有一個輸入框,我們希望在用戶輸入后間隔一段時間再執(zhí)行指定的邏輯。同時,由于防抖需要在項目中多處使用,因此我們希望能夠盡可能方便地重復(fù)使用,并且代碼精簡。
你可以在這里停一下,自己去嘗試嘗試,子辰在這里等你一會。
思考
我們先來看一個簡單的效果:在輸入框中輸入文本,下方會顯示我們輸入的文本。
<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ā)生,而是要等待一小段時間后再發(fā)生變化。
因此不能再使用 v-model,因為 v-model 的雙向綁定是數(shù)據(jù)及時響應(yīng)變化的。
不過我們可以利用 v-model 的特性,將其拆分為 :value=""
和 @input=""
的形式。
<template> <div class="container"> <!-- @input 綁定一個 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ù)進行操作了。
因為這里的防抖無非就是讓數(shù)據(jù)的變化延遲執(zhí)行,所以我們可以這樣寫:
<script setup> import { ref } from 'vue'; const text = ref(''); // 我們在這里定義一個 timer let timer; const handleUpdate = (e) => { // 每一次執(zhí)行這個函數(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: 延遲時間 ( 默認(rèn) 1000ms ) */ export function debounce(func, delay = 1000) { let timer; return function (...args) { clearTimeout(timer); timer = setTimeout(() => { func.call(this, ...args); }, delay); }; }
這樣我們就封裝好了一個防抖函數(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 和 延遲時間傳入進去 // 當(dāng)數(shù)據(jù)變化的時候,運行的是防抖函數(shù) // 那么防抖函數(shù)會隔一段時間再去運行這個 update 函數(shù),去更新數(shù)據(jù) const handleUpdate = debounce(update, 1000) </script>
現(xiàn)在運行一下,效果還是沒問題的。
但是現(xiàn)在看起來還是不夠舒服,而且 v-model 原本用得好好地卻被換成了 :value=""
和 @input=""
的形式。
而且每次使用時還需要寫很多冗余代碼,這我可忍受不了。
但是要繼續(xù)優(yōu)化就需要考驗?zāi)芰α?,我們說過我們要做到極致的防抖!
那當(dāng)然要在最初的代碼模式上做防抖才算最完美。
首先讓我們觀察一下 const text = ref('')
得到了什么。我們在控制臺中輸出 text 看看。
通過輸出結(jié)果可以看出,text 得到了一個叫做 RefImpl 對象。
這個對象里有一個 value 屬性,當(dāng)你看到 value 屬性是 (...)
時,你應(yīng)該立刻明白它是通過Object.defineProperty
定義的,通過定義之后就會產(chǎn)生 get 和 set 兩個函數(shù)。
也就是說,ref 的源代碼看上去就像是 get 和 set 的結(jié)構(gòu)。
根據(jù)上面發(fā)現(xiàn),讓我們寫出 ref 方法的結(jié)構(gòu):
// ref 方法需要一個值,我們寫作 value function ref(value) { // 它返回一個對象 { // 對象中有一個 value 屬性 // 是通過 Object.defineProperty 定義的 // 定義里會有一個 get 和 set 方法 get(){ // get 方法返回 value return value }, set(val){ // set 方法給 value重新賦值 value = val } } } // 大概就是這個樣子 // 但是不要忘記了 vue 是帶有響應(yīng)式的 // 而響應(yīng)式的原理是什么呢? // 就是在 get 的時候進行依賴收集,就是說誰用到了我這個數(shù)據(jù) // 而 set 的時候叫做派發(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 放到一個防抖函數(shù)中延遲執(zhí)行,那么問題就解決了。
那是否意味著我們要重新寫一個 ref 呢?將其源代碼重新實現(xiàn)一遍?其實沒有必要。
因為 vue 提供了一個自定義 ref 入口:customRef。
通過 customRef 可以自定義 get 和 set 方法。
那么通過 customRef 我們就看到了勝利的曙光了,讓我們利用它寫出自己的 ref。
實現(xiàn)
import { customRef } from "vue"; /** * @description: 防抖的 ref * @param {*} value: ref 的 value * @param {*} delay: 延遲時間,表示 value 的更新要在多少時間后觸發(fā) ( 默認(rèn)為 1000ms ) */ export function debounceRef(value, delay = 1000) { let timer; // 我們這里利用 customRef 來自定義 ref return customRef(() => { // customRef 中返回一個對象,對象中包含 get 和 set 函數(shù) return { get() { // 依賴收集 track() return value; }, set(val) { // 使用 setTimeout 實現(xiàn)防抖 clearTimeout(timer); timer = setTimeout(() => { // 派發(fā)更新 trigger() value = val; }, delay); }, }; }); }
上面代碼中我們實現(xiàn)了自定義 ref,并實現(xiàn)了防抖功能。
但缺少最關(guān)鍵依賴收集和派發(fā)更新部分,我們沒有時間重新實現(xiàn)依賴收集源碼和派發(fā)更新源碼。
但是 vue 貼心的考慮到了這一點,它給我們傳入了兩個參數(shù),track 和 trigger,分別表示依賴收集和派發(fā)更新,我們只要在合適的時候調(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> // 在使用時,我們要引入自定義的 ref import { debounceRef } from './debounceRef'; const text = debounceRef(''); </script>
我們輸出 text 到控制臺看看。
可以看出得到的是一個 CustomRefImpl 對象,這個 value 呢,也是一個 get 和 set,只不過現(xiàn)在用的是我們自己寫的 get 和 set。
來試一下效果。
總結(jié)
通過現(xiàn)在的代碼,我們可以輕松實現(xiàn)防抖的功能,而且使用時非常簡單,只需要一個 ref 就可以做到。
這種方式給我們帶來了很大的便利,讓我們在開發(fā)過程中更加高效。
以上就是vue3利用customRef實現(xiàn)防抖的詳細(xì)內(nèi)容,更多關(guān)于vue3 customRef防抖的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue開發(fā)chrome插件,實現(xiàn)獲取界面數(shù)據(jù)和保存到數(shù)據(jù)庫功能
這篇文章主要介紹了vue開發(fā)chrome插件,實現(xiàn)獲取界面數(shù)據(jù)和保存到數(shù)據(jù)庫功能的示例,幫助大家更好的理解和使用vue,感興趣的朋友可以了解下2020-12-12Vue學(xué)習(xí)筆記進階篇之vue-cli安裝及介紹
這篇文章主要介紹了Vue學(xué)習(xí)筆記進階篇之vue-cli安裝及介紹,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07vue項目引入遠(yuǎn)程jweixin-1.2.0.js文件并使用詳解
這篇文章主要介紹了vue項目引入遠(yuǎn)程jweixin-1.2.0.js文件并使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-03-03vue-router 控制路由權(quán)限的實現(xiàn)
這篇文章主要介紹了vue-router 控制路由權(quán)限的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09vue3?element?plus按需引入最優(yōu)雅的用法實例
這篇文章主要給大家介紹了關(guān)于vue3?element?plus按需引入最優(yōu)雅的用法,以及關(guān)于Element-plus按需引入的一些坑,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-03-03Vue-cli3 $ is not defined錯誤問題及解決
這篇文章主要介紹了Vue-cli3 $ is not defined錯誤問題及解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07