建設(shè)網(wǎng)站不顯示添加白名單全網(wǎng)營銷系統(tǒng)
在前端的日常開發(fā)中,經(jīng)常會使用到兩個函數(shù)防抖(Debounce)和節(jié)流(Throttle),防抖函數(shù)可以有效控制在一段時間內(nèi)只執(zhí)行最后一次請求,例如搜索框輸入時,只在輸入完成后才進(jìn)行請求接口。而節(jié)流函數(shù)則是每隔一段時間就執(zhí)行一次請求。
在 React 應(yīng)用開發(fā)時,不同于普通的 js,而且通過 react hook 機(jī)制,可以更方便的實現(xiàn)這兩個功能。
防抖函數(shù)(Debounce)
從上面的圖中可以看出,使用了防抖函數(shù)后,無論我們中間點了多少次,也只會在延時結(jié)束時執(zhí)行一次。
使用 js 簡單實現(xiàn)防抖函數(shù)
function debounce(fn: any, wait: number) {let timer: anyreturn (...args: any) => {// @ts-ignoreconst context = thisif (timer) clearTimeout(timer)timer = setTimeout(() => {timer = nullfn.apply(context,args)}, wait)}
}
防抖的原理比較簡單,就是使用閉包保存住計時器 timer 和 傳遞的函數(shù),然后每次進(jìn)入時都把之前的 timer 清空掉,這樣延時 wait 每次都會從新開始計算,以此來達(dá)到只在延時結(jié)束后執(zhí)行一次的效果。
在 React Input 中使用防抖函數(shù)
假設(shè)有個需求,用戶通過輸入商品名來搜索商品,那么不可能每次用戶輸入時都去請求后臺接口,最好的處理方式就是加上防抖功能,只在用戶輸入完成后請求一次,這樣做可以避免多次無效的調(diào)用后臺接口。
實現(xiàn)這個功能用傳統(tǒng)的方法可以這樣做:
// 防抖函數(shù),間隔時間為 2 秒
const changeDebounce = useCallback(debounce(handleChange, 2000), [])// 搜索框,非受控組件
<Input onChange={(e) => changeDebounce(e)} style={{width: 150, marginRight: 20}}/>
可以看出上面的方式比較適合非受控的組件,如果是受控組件,可以采用 React Hooks 機(jī)制來實現(xiàn):
// 傳遞給 Input 組件的值
const [value, setValue] = useState('')// useEffect 鉤子函數(shù)
useEffect(() => {const getData = setTimeout(() => {if (!value) returninfo('異步請求....')}, 2000)return () => clearTimeout(getData)
}, [value])// 搜索框,受控組件
<Input value={value} onChange={(e) => setValue(e.target.value)} style={{width: 150, marginRight: 20}}/>
可以看出,使用 useEffect 鉤子函數(shù)可以很方便的實現(xiàn)防抖功能,原因就在于依賴了 value 值的變化,每次 value 變化后 useEffect 鉤子都會執(zhí)行清除邏輯,也就是 return 返回的函數(shù),重新執(zhí)行,這樣就保證了多次輸入內(nèi)容后,只有到了間隔時間才會執(zhí)行一次的邏輯。
如果再抽象一點,我們可以把這段邏輯提取成一個自定義 hook:
const useDebounce = <V>(value: V, wait: number) => {const [debounceValue,setDebounceValue] = useState(value)useEffect(() => {const timer = setTimeout(() => setDebounceValue(value),wait)return () => clearTimeout(timer)},[value,wait])return debounceValue
}
在自定義 hook 中,使用了另外一個 state 進(jìn)行保存和更新狀態(tài),只有在間隔時間到了才會更新,然后將這個新的 state 返回出去,在頁面上可以這樣使用,直接依賴 返回出來的狀態(tài),這樣每次這個狀態(tài)改變時,就是間隔時間到了的時候,就可以進(jìn)行異步請求了。
const debounceValue = useDebounce(value,2000)useEffect(() => {if (!debounceValue) returninfo('異步請求....')
},[debounceValue])
最終的效果如下:
節(jié)流函數(shù)(Throttle)
上面的圖比較好看出來節(jié)流函數(shù)的應(yīng)用場景,一般是在滾動屏幕等執(zhí)行次數(shù)很密集的情況下使用,有點限流的意思。
使用 js 實現(xiàn)節(jié)流函數(shù)
function throttle(fn: any, wait: number) {let inThrottle = falsereturn (...args: any) => {// @ts-ignoreconst context = thisif (!inThrottle) {inThrottle = truefn.apply(context, args)setTimeout(() => {inThrottle = false}, wait)}}
}
節(jié)流函數(shù)實現(xiàn)的方式就是通過變量來控制是否執(zhí)行邏輯,這里使用了 inThrottle 這個 boolean 值來進(jìn)行控制,如果時間沒到就一直是 true 的狀態(tài),直到時間到了后開始執(zhí)行邏輯。
下面通過個小例子演示一下沒有使用節(jié)流函數(shù)時,拖動效果:
<div style={{width: 50, height: 50, backgroundColor: 'blue'}} draggable={true} onDrag={() => { info('被拖動了~~~') }}
/>
可以看到,在不試用節(jié)流函數(shù)的情況下,剛一拖動,就執(zhí)行了許多許多次,如果這是請求,肯定是能把接口都刷爆的,所以這種情況一定要使用節(jié)流函數(shù)來控制一下頻率,下面是使用了節(jié)流函數(shù)的效果:
const changeThrottle = useCallback(throttle(() => {info('異步請求....')
}, 2000), [])<div style={{width: 50, height: 50, backgroundColor: 'blue'}} draggable={true}onDrag={changeThrottle}
/>
加了節(jié)流函數(shù)后,無論怎樣快速拖動,執(zhí)行的邏輯也是按照間隔時間的頻率進(jìn)行執(zhí)行的。
在 React 中使用節(jié)流函數(shù)
我們可以像上面的防抖函數(shù)一樣,將節(jié)流函數(shù)也使用 React Hook 來實現(xiàn):
const useThrottle = <V>(value: V, wait: number) =>{const [throttledValue, setThrottledValue] = useState<V>(value)const lastExecuted = useRef<number>(Date.now())useEffect(() => {if (Date.now() >= lastExecuted.current + wait) {lastExecuted.current = Date.now()setThrottledValue(value)} else {const timerId = setTimeout(() => {lastExecuted.current = Date.now()setThrottledValue(value)}, wait)return () => clearTimeout(timerId)}}, [value, wait])return throttledValue
}
這里主要通過時間對比來控制是否更新 throttledValue,以達(dá)到節(jié)流的效果,在組件中可以這樣使用:
const [value, setValue] = useState(0)
const throttledValue = useThrottle(value, 2000)useEffect(() => {if (value === 0) returninfo('throttle 異步請求....')
}, [throttledValue])const handleDrag = () => {setValue(prevState => prevState + 1)
}<div style={{width: 50, height: 50, backgroundColor: 'blue'}} draggable={true} onDrag={handleDrag}
/>
最終的效果和直接使用 throttle 函數(shù)是一樣的。
總結(jié)
在前端開發(fā)中,防抖和節(jié)流函數(shù)幾乎是必備的技能,它們的實現(xiàn)原理都離不開 js 閉包的特性,而在 React 中通過使用自定義的 hook,可以達(dá)到一樣的效果,有些場景下可能還更方便些,但總的來說本質(zhì)還是那樣。