HTML5テクニカルノート
Create React App 入門 08: useMemoフックで無駄な再計算を省く
- ID: FN2102002
- Technique: ECMAScript 2015
- Library: React 17.0.1
関数コンポーネントは、たびたび描画し直されます(「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引数の依存配列です。値の算出に用いる変数(winner
とxIsNext
)を依存配列の要素に加えてください。
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.jsconst 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を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 入門
- Create React App 入門 01: 3×3マスのゲーム盤をつくる
- Create React App 入門 02: クリックしたマス目にXをつける
- Create React App 入門 03: マルバツで勝ち負けを決める
- Create React App 入門 04: クラスのコンポーネントをuseState()で関数に書き替える
- Create React App 入門 05: useContextで状態をコンポーネントツリー内に共有する
- Create React App 入門 06: アプリケーションのロジックをコンテクストに切り出す
- Create React App 入門 07: ゲームの履歴をさかのぼる
- Create React App 入門 08: useMemoフックで無駄な再計算を省く
- Create React App 入門 09: useCallbackフックで無駄な処理を省く
- Create React App 入門 10: 条件によってハンドラは無効にする ー useMemoを使って
作成者: 野中文雄
作成日: 2021年02月07日
Copyright © 2001-2021 Fumio Nonaka. All rights reserved.