源碼鏈接:https://github.com/alibaba/ho...
概述
首先,useDebounceFn和useDebounce都是一個用於添加防抖功能的hook,不同之處在於useDebounceFn給函數添加防抖,而useDebounce用來給值添加防抖。
防抖
某些時候,我們會無法控制所編寫事件的觸發頻率;比如搜索引擎的搜索框對於輸入內容的實時反饋,以及一些根據實時數據動態更新的組件。如果在短時間內觸發大量事件,可能會引起閃爍甚至卡頓。
防抖(Debounce)這個概念是解決此類問題的一種方法(其他的方法比如節流)。
防抖,控制事件只會在:觸發該事件,並在固定時間內未觸發第二次時,才會執行。若短時間內該事件被連續觸發多次,則會在最後一次觸發的固定時間後再執行此事件。
useDebounceFn
該hook用來包裝所需要的函數,給函數添加防抖動的功能。
簡單用法
以下的代碼將會在點擊按鈕並1000毫秒內未再次點擊按鈕時,將value變更為2。
import { useDebounceFn } from 'ahooks';
import React, { useState } from 'react';
export default () => {
const [value, setValue] = useState(0);
const { run } = useDebounceFn(
(newValue) => {
setValue(newValue);
},
{
wait: 1000,
leading
},
);
return (
<div>
<p style={{ marginTop: 16 }}> Clicked count: {value} </p>
<button type="button" onClick={run(2)}>
chuange to 2
</button>
</div>
);
};
參數説明
配置參數
- wait:防抖等待時間,默認值為1000ms(number)
- leading:是否在延遲開始前調用函數,默認為false
- trailing:是否在延遲開始後調用函數,默認為true
- maxWait:最大等待時間,單位為ms
結果參數
- run:觸發執行傳入hook的函數,參數即為函數需要的參數
- cancel:取消當前防抖
- flush:立即調用當前防抖函數
源碼分析
isDev
if (isDev) {
if (!isFunction(fn)) {
console.error(`useDebounceFn expected parameter is a function, got ${typeof fn}`);
}
}
isDev是自定義的一個環境變量,用來判斷當前是否處於開發環境。
useLatest
const fnRef = useLatest(fn);
useLatest也是ahooks的一個hook,一般是用來返回某個state的最新值,避免閉包問題(比如上一篇文章中提到過的,useCallback的閉包陷阱)。
debounce
const debounced = useMemo(
() =>
debounce(
(...args: Parameters<T>): ReturnType<T> => {
return fnRef.current(...args);
},
wait,
options,
),
[],
);
debounce是lodash中的方法,用來解決包裝函數解決防抖問題。這裏使用useMemo緩存debounce這個方法,同樣是為了減少消耗。下面封裝run、cancel、flush時,也是直接使用debounce的方法。
Parameters是typescript的告誡類型,用於獲取函數T的參數類型,而ReturnType則是用來獲取函數T的返回類型。
fnRef是使用useLatest封裝好的,傳入函數的最新值。
useUnmount
useUnmount(() => {
debounced.cancel();
});
useUnmount也是ahooks封裝好的hook,用來設置在組件卸載時執行的函數;為了防止內存泄露,當組件卸載時需要取消對debounce的調用。
附上源碼
import debounce from 'lodash/debounce';
import { useMemo } from 'react';
import type { DebounceOptions } from '../useDebounce/debounceOptions';
import useLatest from '../useLatest';
import useUnmount from '../useUnmount';
import { isFunction } from '../utils';
import isDev from '../utils/isDev';
type noop = (...args: any[]) => any;
function useDebounceFn<T extends noop>(fn: T, options?: DebounceOptions) {
//判斷是否開發環境
if (isDev) {
if (!isFunction(fn)) {
console.error(`useDebounceFn expected parameter is a function, got ${typeof fn}`);
}
}
const fnRef = useLatest(fn);
const wait = options?.wait ?? 1000;
const debounced = useMemo(
() =>
debounce(
(...args: Parameters<T>): ReturnType<T> => {
return fnRef.current(...args);
},
wait,
options,
),
[],
);
useUnmount(() => {
debounced.cancel();
});
return {
run: debounced,
cancel: debounced.cancel,
flush: debounced.flush,
};
}
export default useDebounceFn;
useDebounce
分析過useDebounceFn後,useDebounce的源碼較為簡單,直接用useState創建一個debounced,然後使用useDebounceFn封裝它的更新方法setDebounced即可。
useEffect
該hook在每次依賴項改變時會執行,與usecallback不同的是,當依賴項為空時,該hook每次重新渲染都會執行。用在這裏,當傳入的參數改變時就調用一次debounce的run(當然還是遵循防抖動的規則)
源碼
function useDebounce<T>(value: T, options?: DebounceOptions) {
const [debounced, setDebounced] = useState(value);
const { run } = useDebounceFn(() => {
setDebounced(value);
}, options);
useEffect(() => {
run();
}, [value]);
return debounced;
}