【React x TypeScript】useDebounce を作る
こんにちは Gaji-Labo フロントエンドエンジニアの茶木です。
input や scroll など連続して発生するイベントで遅延が発生するほど高負荷ならば、処理をまびく debounce の出番ですね。onScroll や onChange の処理結果の多くは、後続の処理で上書きされるので、連続した処理の最後だけ行って、途中の処理はスキップしても問題ないものがほとんどだからです。
React では hook の形で準備すると便利です。
基本形
debounce の機能は、処理(A)を少し遅らせて実行する timeout の処理と、その実行前に次の処理(B)の要求を受けたときにAの timeout をクリアして、Bの timeout をセットすることです。
理解のために、素直に書くとこうです。
let timerId:NodeJS.Timeout;
const debounce = (callback:() => void) => {
if(timerId) {
clearTimeout(timer.current);
timer.current = undefined;
}
timerId = setTimeout(callback, 40);
}timeoutが動いていれば( = 実行待ちの古い処理があれば)破棄する- 処理を
timeoutで遅延実行する
やってることは簡単ですね。
debounce をカスタムフックにする
基本形では、debounce の引数に呼び出す関数を入れていました。そのため例えば onChange で使う場合は毎回 debounce でくるむ形になってしまいそうです。
onChange={debounce(() => handleInput(e))}debounce を気にせずロジックに集中して書けるとなお良いですよね。
上記を加味したカスタムフックの debounce を作ってみました。
const useDebounce = <T>(
callback: (arg: T) => void,
delay: number
): [(arg: T) => void, () => void] => {
const timer = useRef<NodeJS.Timeout>();
const clearDebounce = useCallback(() => {
if (timer.current) {
clearTimeout(timer.current);
timer.current = undefined;
}
}, []);
const debounce = useCallback(
(arg: T) => {
clearDebounce();
timer.current = setTimeout(() => {
callback(arg);
}, delay);
},
[callback, clearDebounce, delay]
);
return [debounce, clearDebounce];
};変更点
いくつか基本形からの変更があるので解説します。
callback は事前登録
callbackはonChangeの時点で書かずに済むように、useDebounceの引数に入れています。- あわせて
callbackの引数(多くはe, イベントオブジェクトになるでしょう)を受け取れるようにしています。
debounde の破棄用のメソッド clearDebounce
useDebounceが返す配列に、手動で現在登録中のdebounceを破棄するメソッドclearDebounceを含めました。
useDebounceを呼び出して使う
オリジナルのイベントハンドラ想定のメソッドを、 useDebounceに処理遅延の時間と一緒に食わせると、debounce機能付きのイベントハンドラになります。
これで onChangeは、中で debounce が行われてることなど気にせず記述ができます。
const changeInput = (e) => { /* do something */ }
const [handleInput, clearDebounceHandleInput] = useDebounce(changeInput, 40);
return (
<input onChange={handleInput}>
);また clearDebounce を使って、たとえば、コンポーネントが破棄されるタイミングで未処理のdebounceが残っていると不都合がある場合に cleanup に指定して処理を破棄できます。
useEffect(() => {
return clearDebounceHandleInput;
})これで安心して重たい処理が input や scroll で使えますね!
(そもそもイベントハンドラは重たくならないように気をつけないといけないけど・・・)
Gaji-Laboでは、React経験が豊富なフロントエンドエンジニアを募集しています
弊社ではReactの知見で事業作りに貢献したいフロントエンドエンジニアを募集しています。大きな制作会社や事業会社とはひと味もふた味も違うGaji-Laboを味わいに来ませんか?
もちろん、一緒にお仕事をしてくださるパートナーさんも随時募集中です。まずはお気軽に声をかけてください。お仕事お問い合わせや採用への応募、共に大歓迎です!
求人応募してみる!




