【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を味わいに来ませんか?
もちろん、一緒にお仕事をしてくださるパートナーさんも随時募集中です。まずはお気軽に声をかけてください。お仕事お問い合わせや採用への応募、共に大歓迎です!
求人応募してみる!