2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

React + TypeScript: use-debounceでコードの繰り返し実行を間引く

Last updated at Posted at 2023-12-07

同じコードが短い時間に繰り返し実行されるとき、処理を間引きたい場合があります。そのような制御(debounce)を行うライブラリがuse-debounceです。

  • 間引き処理に特化しているため、容量は1KBにも及びません。
  • 使い方が標準的で、実装はUnderscoreLodashと同じです。
  • サーバーレンダリングにも対応しています。

間引きの処理が必要な例として本稿で採り上げるお題は、公式サイトを参考にしたつぎのふたつです。

  • テキストフィールドに入力される値の変更を間引く。
    • 値をAPIにリクエストするような場合に送信の負荷を下げる。
  • 画面がスクロールされるときに再描画を減らす。
    • スクロールする範囲が大きい場合に逐次描画し直さない。

インストール

インストールは、npmまたはyarnでつぎのように行ってください。

npm install --save use-debounce
yarn add use-debounce

useDebounceで値の変更を間引く

まずは、間引きをしないテキストフィールドへの入力のコード例です。<input>要素にひと文字入力するたびに、onChangeハンドラで状態変数(text)が更新されます。値は画面に「Actual value」(<p>要素)として示しました。

src/Input.tsx
import { useState } from 'react';
import type { FC } from 'react';

export const Input: FC = () => {
	const [text, setText] = useState('Hello');
	return (
		<div>
			<input
				onChange={({ target: { value } }) => {
					setText(value);
				}}
			/>
			<p>Actual value: {text}</p>
		</div>
	);
};

use-debounceの中でもっともシンプルな関数がuseDebounceです。変更を監視する値(value)とその間隔のミリ秒(delay)を引数に渡します。戻り値は配列で、第1要素(state)が間引き後の値です。

const [state] = useDebounce(value, delay);

引数

  • value: 変更を監視する値1
  • delay: 監視間隔のミリ秒。

戻り値

  • [state]: 間引き後の状態値を第1要素にもつ配列。

useDebounce関数には、テキスト入力フィールドの状態変数(text)と監視間隔(1000)を渡します。戻り値から得られる間引き後の値(value)は、画面に「Debounce value:」(<p>要素)として加えました。テキストフィールドの値が、入力のたびに変わることは同じです。けれど、間引き後の値は、入力を指定間隔止めなければ変更されません。以下のサンプル001に掲げたのはCodeSandboxに公開した作例です。動きや各モジュールのコードについては、こちらをご参照ください。

src/Input.tsx
import { useDebounce } from 'use-debounce';

export const Input: FC = () => {

	const [value] = useDebounce(text, 1000);
	return (
		<div>
			<input
				defaultValue={"Hello"}
    
			/>

			<p>Debounce value: {value}</p>
		</div>
	);
};

サンプル001■React + TypeScript: useDebounce example with use-debounce

useDebouncedCallbackで関数の呼び出しを間引く

単純な値の変更ではなく、コールバック関数の呼び出しを間引くのがuseDebouncedCallbackです。第1引数にコールバック(func)、第2引数には間引く呼び出し間隔(wait)を渡してください。戻り値(debounced)が呼び出しの間引かれる新たな関数です。

const debounced = useDebouncedCallback(func, wait);

引数

  • func: 呼び出しを間引くコールバック関数。
  • wait: 呼び出しを間引く間隔のミリ秒。

戻り値

  • debounced: 呼び出しが間引かれる新たなコールバック関数。

もとにするコードは、前項と同じ冒頭のテキスト入力フィールドの例にしましょう。呼び出しが間引かれるコールバック関数(debounced)は、onChangeハンドラに定めます。すると、ハンドラ関数が状態変数(text)を書き替えますので、間引かれたあとの値は別の状態変数にとっておく必要はありません(前掲サンプル001の変数value)。

src/Input.tsx
import { useDebouncedCallback } from 'use-debounce';

export const Input: FC = () => {

	const debounced: ChangeEventHandler<HTMLInputElement> = useDebouncedCallback(
		({ target: { value } }) => {
			setText(value);
		},
		1000,
	);
	return (
		<div>

			<input
				defaultValue={"Hello"}
				/* onChange={({ target: { value } }) => {
					setText(value);
				}} */
				onChange={debounced}
			/>
			{/* <p>Actual value: {text}</p> */}
			<p>Debounce value: {text}</p>
		</div>
	);
};

これで、状態変数(text)は間引かれたあとの値(「Debounce value」)になりました(サンプル002)。テキストフィールド(<input>要素)の表示はキー入力するたびに変わるので、これが実際の値(「Actual value」)です。

サンプル002■React + TypeScript: useDebouncedCallback example 01 with use-debounce

useDebouncedCallbackでスクロール画面の再描画を抑える

本稿のふたつめのお題です。スクロール画面の再描画をuseDebouncedCallbackで抑えてみましょう。画面のscrollイベントは、ライブラリSubscribe-eventで捉えることにします。少し古いものの、扱いが簡単で小さなライブラリです。npmまたはyarnでインストールしてください。

npm install --save subscribe-event
yarn add subscribe-event

イベントを購読するsubscribe関数の構文はつぎのとおりです。戻り値(unsubscribe)が購読終了の関数であることにご注意ください。

const unsubscribe = subscribe(element, event, eventCallback);

引数

  • element: イベントが引き起こされる要素。
  • event: 購読するイベント。
  • eventCallback: イベントのコールバック関数。

戻り値

  • unsubscribe: イベントの購読を破棄する関数。

先に、useDebouncedCallbackは使わない画面のスクロールです。windowscrollイベントに応じてコールバック関数を呼び出します。ここでは、useEffectに定めました。関数は単にwindow.pageYOffsetの値を状態変数(position)に与えているだけです。コンポーネントアンマウント時の購読破棄(unsubscribe)は、useEffectの戻り値であるクリーンアップ関数から呼び出します。

状態変数値(position)は、確認できるよう画面のテキスト「Debounced top position」(<p>要素)に示しました。もうひとつ数値を与えたのが、refオブジェクト(updatedCount)です(初期値0)。コンポーネント(ScrolledComponent)が描画されるたびにカウントアップされます。この再描画回数(updatedCount.current)も、テキスト「Component rerendered nnn times」(<p>要素)として画面に加えました。スクロールを続けていると、回数はどんどん増えることが確かめられるでしょう。

src/ScrolledComponent.tsx
import { useEffect, useRef, useState } from 'react';
import type { FC } from 'react';
import subscribe from 'subscribe-event';

export const ScrolledComponent: FC = () => {
	const updatedCount = useRef(0);
	updatedCount.current++;
	const [position, setPosition] = useState(window.pageYOffset);
	useEffect(() => {
		const unsubscribe = subscribe(window, 'scroll', () => {
			setPosition(window.pageYOffset);
		});
		return () => {
			unsubscribe();
		};
	}, []);
	return (
		<div style={{ height: 10000 }}>
			<div style={{ position: "fixed", top: 0, left: 0 }}>
				<p>Debounced top position: {position}</p>
				<p>Component rerendered {updatedCount.current} times</p>
			</div>
		</div>
	);
};

そこで用いるのがuseDebouncedCallbackです。第1引数に渡すコールバック関数で、状態変数(position)を設定します。そして、戻り値の関数debouncedは、subscribeの第3引数に与えてください。

src/ScrolledComponent.tsx
import { useDebouncedCallback } from 'use-debounce';

export const ScrolledComponent: FC = () => {

	const debounced = useDebouncedCallback(
		() => {
			setPosition(window.pageYOffset);
		},
		800,
	);
	useEffect(() => {
		/* const unsubscribe = subscribe(window, 'scroll', () => {
			setPosition(window.pageYOffset);
		}); */
		const unsubscribe = subscribe(window, 'scroll', debounced);
		return () => {
			unsubscribe();
		};
	}, []);

};

useDebouncedCallbackでスクロール時の画面再描画を抑えたのがつぎのサンプル003です。スクロールする手を止めないかぎり、コンポーネント(ScrolledComponent)の再描画のカウンターは数値が上がりません。

サンプル003■React + TypeScript: useDebouncedCallback example 02 with use-debounce

  1. 値が変更されたかどうかを確かめるのは、デフォルトでは単純な等価比較(===)です。そのため、対象がオブジェクトのときは、useDebouncedCallbackでプロパティ値を取り出して調べなければなりません(useDebounceの第3引数にオプションオブジェクトで比較関数を渡すことはできます)。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?