サイトトップ

Director Flash 書籍 業務内容 プロフィール

HTML5テクニカルノート

Create React App 入門 08: useMemoフックで無駄な再計算を省く


関数コンポーネントは、たびたび描画し直されます(「useEffect / React Hooks – React入門」「functionコンポーネントの再描画」参照)。すると、表示のために用いられる値も、そのたびに計算が繰り返されるのです。useMemoフックは、計算に使われている値が変わらず、同じ結果となる場合には、前回の算出値を覚えておいて(memorize)使います。いわばキャッシュのようなものです(「計算結果のメモ化はどのように行うのですか?」参照)。プログラミングの用語では「メモ化(memorization)」と呼ばれます。

01 useMemoフックを使う

useMemoフックの構文はつぎのとおりです。useMemoにはふたつの引数を渡します。第1引数は、値を算出して返す関数です。この戻り値がメモ化されて、useMemoフックから返されます。第2引数は依存配列と呼ばれ、要素は値の算出に用いる変数や関数の参照です。依存配列の要素の値が変わると再計算が行われます。構文としては、第2引数は省いても構いません。ただそうすると、関数コンポーネントが描画されるたびに再計算されるので、フックを使う意味がなくなります。依存配列を空[]にすると、関数コンポーネントがはじめて描画されるときに計算が行われ、そのあと再計算はされません。


import { useMemo } from 'react';

const メモ化された値 = useMemo(() =>
	// 計算処理
	return 算出値;
, [依存配列]);

02 ゲーム情報表示のコンポーネントの算出値をメモ化する

useMemoフックでメモ化するのは、ゲーム情報表示のモジュールsrc/components/GameInfo.jsの算出値です。まず、つぎの差し手あるいは勝者が示されるテキスト(status)を、つぎのようにuseMemoフックに定めます。大切なのは、第2引数の依存配列です。値の算出に用いる変数(winnerxIsNext)を依存配列の要素に加えてください。

src/components/GameInfo.js

// import { useContext } from 'react';
import { useContext, useMemo } from 'react';

const GameInfo = () => {

	// const status = (winner) ?
	const status = useMemo(() => (winner) ?
		`Winner: ${winner}` :
		`Next player: ${xIsNext ? 'X' : 'O'}`  // ;
	, [winner, xIsNext]);

	return (
		<div className="game-info">
			<div>{status}</div>

		</div>
	);
};

つぎに、ボタンのリストをJSX要素の配列として算出する式の値(moves)です。式を戻り値とする関数にして、useMemoフックで包みます。第2引数の依存配列を適切に与えてください。書き改めたモジュールの記述全体は、以下のコード001のとおりです。

src/components/GameInfo.js

const GameInfo = () => {

	// const moves = history.map((_, move) => {
	const moves = useMemo(() => history.map((_, move) => {

		return (
			<li key={move}>
				<button onClick={() => jumpTo(move)}>{desc}</button>
			</li>
		);
	// });
	}), [history, jumpTo]);
	return (
		<div className="game-info">

			<ol>{moves}</ol>
		</div>
	);
};

コード001■useMemoで算出値をメモ化したゲーム情報表示のコンポーネントモジュール

src/components/GameInfo.js

import { useContext, useMemo } from 'react';
import { GameContext } from './GameContext';

const GameInfo = () => {
	const { jumpTo, history, winner, xIsNext } = useContext(GameContext);
	const status = useMemo(() => (winner) ?
		`Winner: ${winner}` :
		`Next player: ${xIsNext ? 'X' : 'O'}`
	, [winner, xIsNext]);
	const moves = useMemo(() => history.map((_, move) => {
		const desc = move ?
			'Go to move #' + move :
			'Go to game start';
		return (
			<li key={move}>
				<button onClick={() => jumpTo(move)}>{desc}</button>
			</li>
		);
	}), [history, jumpTo]);
	return (
		<div className="game-info">
			<div>{status}</div>
			<ol>{moves}</ol>
		</div>
	);
};

export default GameInfo;

03 マス目のコンポーネントの算出値をメモ化する

もうひとつ、マス目のコンポーネントモジュールsrc/components/Square.jsにもuseMemoフックが使えます。差し手の履歴の配列(history)から盤面の9コマの配置を取り出して、マス目のJSXの要素として算出する式(squares)です。これでマルバツゲームのアプリケーションにおける算出値の無駄な再計算が省かれます。ゲームの動きそのものは変わりません(図001)。書き直したマス目のコンポーネントモジュールの記述全体は、以下のコード002にまとめたとおりです。

src/components/Square.js

// import { useContext } from 'react';
import { useContext, useMemo } from 'react';

const Square = ({ id }) => {

	// const squares = [...history[stepNumber].squares];
	const squares = useMemo(() => [...history[stepNumber].squares], [history, stepNumber]);
	return (
		<button

		>
			{squares[id]}
		</button>
	);
};

図001■マルバツゲームの動きは変わらない

図001

無駄をなくすように書き替えたモジュールふたつのスクリプトは、つぎのコード001にまとめたとおりです。また、サンプル001をCodeSandboxに公開しました。

コード002■useMemoで算出値をメモ化したマス目のコンポーネントモジュール

src/components/Square.js

import { useContext, useMemo } from 'react';
import { GameContext } from './GameContext';

const Square = ({ id }) => {
	const { onClick, history, stepNumber } = useContext(GameContext);
	const squares = useMemo(() => [...history[stepNumber].squares], [history, stepNumber]);
	return (
		<button
			type="button"
			className="square"
			onClick={() => onClick(id)}
		>
			{squares[id]}
		</button>
	);
};

export default Square;

サンプル001■Create React App: Tic Tac Toe 08

Create React App 入門


作成者: 野中文雄
作成日: 2021年02月07日


Copyright © 2001-2021 Fumio Nonaka.  All rights reserved.