React Hook でのスロットルとデバウンス
React Hook でのスロットルやデバウンスの使い方
スロットル (Throttle) と デバウンス (Debounce) は、イベントハンドラーや関数呼び出しの頻度を制限するテクニックです。特に、頻繁に発生するイベントや高コストな関数を効率的に処理するために使用されます。
スロットル (Throttle)
スロットルは、一定時間内に発生したイベントのうち、最初のイベントのみを実行します。これにより、イベントの発生頻度を制限し、パフォーマンスを向上させることができます。
import { useState, useEffect } from 'react';
import throttle from 'lodash/throttle';
function MyComponent() {
const [value, setValue] = useState('');
useEffect(() => {
const throttledHandler = throttle(
(event) => {
setValue(event.target.value);
},
500 // 500ms のスロットル時間
);
// イベントリスナーの追加
document.addEventListener('input', throttledHandler);
// クリーンアップ関数
return () => {
document.removeEventListener('input', throttledHandler);
};
}, []);
return (
<input type="text" value={value} onChange={throttledHandler} />
);
}
デバウンス (Debounce)
デバウンスは、一定時間内にイベントが発生し続けた場合、最後のイベントのみを実行します。これにより、イベントの発生が完了するまで処理を遅延させることができます。
import { useState, useEffect } from 'react';
import debounce from 'lodash/debounce';
function MyComponent() {
const [value, setValue] = useState('');
useEffect(() => {
const debouncedHandler = debounce(
(event) => {
setValue(event.target.value);
},
500 // 500ms のデバウンス時間
);
// イベントリスナーの追加
document.addEventListener('input', debouncedHandler);
// クリーンアップ関数
return () => {
document.removeEventListener('input', debouncedHandler);
};
}, []);
return (
<input type="text" value={value} onChange={debouncedHandler} />
);
}
注意
- イベントリスナーの追加と削除を適切に行い、メモリリークを防ぐようにしてください。
- スロットル時間やデバウンス時間は、アプリケーションの要件に応じて適切に設定してください。
lodash
のthrottle
やdebounce
を使用していますが、他のライブラリや自作関数でも実装可能です。
コードの全体的な流れ
- useState
入力値の状態を管理するための Hook を導入します。 - useEffect
コンポーネントがマウントされた際に、スロットルまたはデバウンス処理をセットアップします。- イベントリスナーを追加し、イベントが発生するたびにスロットルまたはデバウンス関数が呼び出されるようにします。
- クリーンアップ関数でイベントリスナーを削除し、メモリリークを防ぎます。
- throttle/debounce
lodash
のthrottle
またはdebounce
関数を使用して、スロットルまたはデバウンス処理を行います。- 第一引数にイベントハンドラーを渡し、第二引数にスロットル時間またはデバウンス時間をミリ秒単位で指定します。
- イベントハンドラー
スロットルまたはデバウンスされたイベントハンドラー内で、入力値を状態に更新します。
コードの詳細な解説
// スロットルの例
import { useState, useEffect } from 'react';
import throttle from 'lodash/throttle';
function MyComponent() {
const [value, setValue] = useState('');
useEffect(() => {
const throttledHandler = throttle(
(event) => {
setValue(event.target.value);
},
500 // 500ms のスロットル時間
);
// イベントリスナーの追加
document.addEventListener('input', throttledHandler);
// クリーンアップ関数
return () => {
document.removeEventListener('input', throttledHandler);
};
}, []);
return (
<input type="text" value={value} onChange={throttledHandler} />
);
}
- イベントハンドラー
onChange
イベントが発生すると、throttledHandler
が呼び出されます。throttledHandler
内では、event.target.value
を取得し、setValue
を使って状態を更新します。
- useEffect
コンポーネントがマウントされた際に実行されます。throttle
関数を使って、500ミリ秒ごとに一度だけsetValue
関数が呼び出されるようにthrottledHandler
を作成します。document.addEventListener
でinput
イベントが発生するたびにthrottledHandler
が実行されるように設定します。- クリーンアップ関数で
document.removeEventListener
を呼び出し、イベントリスナーを削除します。これにより、コンポーネントがアンマウントされた際に、イベントリスナーが残り続けることを防ぎます。
デバウンスの例
デバウンスの例は、スロットルの例とほぼ同じです。throttle
を debounce
に置き換えるだけです。
// デバウンスの例
import { useState, useEffect } from 'react';
import debounce from 'lodash/debounce';
// ... (上記のスロットルの例と同様)
重要なポイント
- 他のライブラリ
lodash
以外にも、スロットルやデバウンスを実装するためのライブラリが存在します。 - スロットル時間やデバウンス時間の調整
アプリケーションの要件に合わせて、スロットル時間やデバウンス時間を調整します。 - イベントリスナーの管理
useEffect
のクリーンアップ関数でイベントリスナーを削除することで、メモリリークを防ぎます。 - スロットルとデバウンスの違い
スロットルは一定時間内に発生したイベントのうち、最初のイベントのみを実行し、デバウンスは一定時間内にイベントが発生し続けた場合、最後のイベントのみを実行します。
React Hook を使用して、スロットルやデバウンスを簡単に実装することができます。これにより、頻繁に発生するイベントによるパフォーマンス低下を防ぎ、よりスムーズなユーザー体験を提供できます。
- useDeferredValue
React 18 から導入されたuseDeferredValue
は、レンダリングの最適化に特化した Hook です。スロットルやデバウンスとは異なる目的で使用されます。
カスタム Hook を作成する
- デメリット
- コード量が増える可能性がある
- 初期実装に時間がかかる
- メリット
- 再利用性が高まる
- ロジックをカプセル化できる
- プロジェクト固有のニーズに合わせてカスタマイズできる
import { useState, useEffect, useRef } from 'react';
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(va lue);
const timerRef = useRef(null);
useEffect(() => {
clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
setDebouncedValue(value);
}, delay);
}, [value, delay]);
return debouncedValue;
}
RxJS を利用する
- デメリット
- 学習コストが高い
- RxJS の導入が必要
- メリット
- 非同期処理の記述が簡単
- パイプライン処理で複雑なロジックを表現できる
- 大規模なアプリケーションに適している
import { fromEvent, debounceTime } from 'rxjs';
function MyComponent() {
const [value, setValue] = useState('');
useEffect(() => {
const subscription = fromEvent(document, 'input')
.pipe(debounceTime(500))
.subscribe((event) => {
setValue(event.target.value);
});
return () => {
subscription.unsubscribe();
};
}, []);
// ...
}
React のライフサイクルメソッドを利用する
- デメリット
- 関数型コンポーネントでは使いづらい
- 複雑なロジックには不向き
- メリット
- シンプルな実装
- React の基礎的な知識で実装可能
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { value: '' };
this.timeoutId = null;
}
handleChange = (event) => {
clearTimeout(this.timeoutId);
this.timeoutId = setTimeout(() => {
this.setState({ value: event.target.value });
}, 500);
};
// ...
}
requestAnimationFrame を利用する
- デメリット
- メリット
- ブラウザのレンダリングサイクルに同期して処理を実行できる
- 高度なアニメーションやインタラクションに適している
function MyComponent() {
// ...
useEffect(() => {
let lastTime = performance.now();
const handleInputChange = (event) => {
const now = performance.now();
if (now - lastTime > 500) {
// 500ms 以上経過していたら処理を実行
setValue(event.target.value);
lastTime = now;
}
};
// ...
}, []);
}
どの方法を選ぶべきか
- 高性能なアニメーションやインタラクション
requestAnimationFrame が適している - 大規模なプロジェクトでRxJSに慣れている
RxJS を利用すると、複雑な非同期処理を効率的に記述できる - シンプルで小さなプロジェクト
カスタム Hook や React のライフサイクルメソッドが適している
スロットルやデバウンスの実装方法は、プロジェクトの規模、開発者のスキル、パフォーマンス要求など、様々な要因によって最適なものが異なります。それぞれの方法の特徴を理解し、適切な方法を選択することが重要です。
- useMemo
高コストな計算結果をキャッシュしたい場合にuseMemo
を利用できます。
選択のポイント
- シンプルさ
React のライフサイクルメソッドはシンプル - パフォーマンス
requestAnimationFrame は高性能 - 学習コスト
RxJS は学習コストが高い - 再利用性
カスタム Hook は再利用性が高い
reactjs lodash react-hooks