はじめに
デバウンスとは、特定の処理が高頻度で呼び出されるのを防ぐテクニックの1つです。
デバウンスを使用すると、特定の時間が経過するまで処理の実行を遅延させることができます。この遅延中に再度処理が呼び出されるとタイマーはリセットされ、再度処理の実行を遅延します。
例としてユーザーが検索のためにテキストボックスへ入力するシーンを考えます。
テキストボックスへ入力した内容が変更されるたびに検索処理が走るような仕様の場合、大量の処理が押し寄せてくるのでパフォーマンスの低下につながります。
そこで、デバウンスを導入することで検索処理が一定の入力ごとに行われ、パフォーマンスの低下を防げます。
Reactでは、デバウンスは主にユーザーの入力やウィンドウのリサイズなど、高頻度で発生するイベントを制御するために使用されます。
デバウンス無しの処理を見る
まずは、比較対象となるデバウンス無しのコンポーネントを作ります。
useStateを用いて入力したテキストを準備し、テキストが更新されて1秒経ってからalertを出するようにしました。テキストが更新されると下部に入力したテキストの文字数が出るようにしました。
今回は長さを求めるだけなのでパフォーマンスに影響はありませんが、外部との通信を同じタイミングで行った場合にトラフィックの増加が想像できます。
また、入力は短期間で行われることが予想されるので実装方法によっては結果の出力順が前後してしまいます。
状態の更新がデバウンスされるhooksを作る
テキストエリアの入力はそのまま行わせたい一方、特定の処理は一定の入力置きに行いたいので、テキストエリアの値を管理する状態と特定の処理を行うように監視する状態を分けることを考えます。
const [text, setText] = useState('');
const displayText = text;
textがテキストエリアを管理する状態で、displayTextはtextを渡しただけの値です。
これでは分けた意味がないのでdisplayTextの更新のタイミングをテキストエリアへの入力が完了して1秒後に行わせます。
このようにする場合displayTextは単純にtextから生成できないのでuseStateで状態を作るようにします。さらに、タイミングの調整にsetTimeoutを利用するのでuseEffectを用いて記述します。
const [text, setText] = useState('');
const [displayText, setDisplayText] = useState(text);
useEffect(() => {
const timeoutID = setTimeout(() => {
setDisplayText(value);
}, 1000);
return () => {
clearTimeout(timeoutID);
};
}, [text])
これでtextが変更されてから1秒後にdisplayTextを更新するようになりました。
useEffectの処理は初回描画後とtextが変更されるたびに発火します。そして、中では変更された1秒後にdisplayTextの値をtextの値に変更させています。
そして、1秒経つ前にtextが再度変更されると、再描画が起きるので次のuseEffectの処理が走る前に前回のクリーンアップ関数が走って1秒後の更新がキャンセルされます。これによって入力して1秒経つまではdisplayTextの更新が遅延されて特定の処理が高頻度で走ることを防げます。
これをhooksで切り出すと以下のようになります。deplayは今回だと1000で、遅延させる秒数を入力します。
const useDebounce = <T extends any>(value: T, delay: number) => {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const timeoutID = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(timeoutID);
};
}, [value, delay]);
return debouncedValue;
}
デバウンス有りの処理を見る
テキストが変更に合わせて内容が下部に入力した文字の長さが記述されていたところから、テキストエリアの入力が終わってから1秒後に行わせます。
入力が完了して1秒後に最後の値で処理が発火します。このようにすることでテキストエリアの入力のたびに特定の処理が走らないのでパフォーマンスの低下や処理の結果が前後することを防げます。
問題はテキストエリアを入力してから1秒後のように特定の秒数に信頼を置く必要がある点です。程よい遅延時間を取り繕って設定する必要があるので慎重に検討しましょう(今回の例では時間を取りすぎて利用者のストレスになりそうです)。
おわりに
Reactの状態をデバウンスさせる方法を紹介しました。
状態の更新が過多で外部との通信が多量になっている場合はこちらを導入してみてはいかがでしょうか。