サイトトップ

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

HTML5テクニカルノート

Create React App 入門 09: useCallbackフックで無駄な処理を省く


前回は、useMemoフックを用いて、算出値の計算が無駄に繰り返されないようにメモ化しました。今回は関数の呼び出しです。関数コンポーネントに定められた関数も、再描画のたびにつくり直されます。関数の生成をメモ化するのがuseCallbackフックです。

01 useCallbackフックを使う

useCallbackフックの構文は、useMemoとよく似ています。違いは第1引数の関数が呼び出すコールバックだということです。第2引数には、useMemoと同じ依存配列を渡します。


import { useCallback } from 'react';

const メモ化された関数 = useCallback(コールバック関数, [依存配列]);

02 コンテクストモジュールのハンドラ関数をメモ化する

まず、コンテクストのモジュールsrc/components/GameContext.jsuseCallbackフックを用いましょう。ひとつめは、盤面のマス目をクリックしたときに呼び出すハンドラ関数(handleClick())です。ハンドラをuseCallbackフックの第1引数、コールバックとして包みます。useMemoと同じく、第2引数の依存配列を正しく与えることが大切です。

src/components/GameContext.js

// import { createContext, useState } from 'react';
import { createContext, useCallback, useState } from 'react';

export const GameProvider = ({ children }) => {

	// const handleClick = (i) => {
	const handleClick = useCallback((i) => {

	// };
	}, [finished, history, stepNumber, xIsNext]);

};

コンテクストモジュールsrc/components/GameContext.jsに定めるコールバック関数はもうひとつあります。ゲーム情報のコンポーネントのボタンリストをクリックしたときのハンドラ(jumpTo())です。つぎのように、useCallbackフックを加えましょう。呼び出す関数をフックでメモ化したモジュールの記述全体は、以下のコード001のとおりです。

src/components/GameContext.js

export const GameProvider = ({ children }) => {

	// const jumpTo = (step) => {
	const jumpTo = useCallback((step) => {

	// };
	}, [history]);

};

コード001■呼び出す関数をuseCallbackでメモ化したコンテクストモジュール

src/components/GameContext.js

import { createContext, useCallback, useState } from 'react';

const initialContext = {
	history: [
		{squares: Array(9).fill(null)},
	],
	xIsNext: true,
	winner: null,
	stepNumber: 0,
};
export const GameContext = createContext(initialContext);
export const GameProvider = ({ children }) => {
	const [history, setHistory] = useState(initialContext.history);
	const [xIsNext, setXIsNext] = useState(initialContext.xIsNext);
	const [finished, setFinished] = useState(false);
	const [winner, setWinner] = useState(initialContext.winner);
	const [stepNumber, setStepNumber] = useState(initialContext.stepNumber);
	const handleClick = useCallback((i) => {
		const _history = history.slice(0, stepNumber + 1);
		const _squares = [..._history[_history.length - 1].squares];
		if (_squares[i]) { return; }
		if (finished) { return; }
		_squares[i] = xIsNext ? 'X' : 'O';
		setHistory([..._history, { squares: _squares }]);
		setXIsNext(!xIsNext);
		setStepNumber(_history.length);
		const _winner = calculateWinner(_squares);
		setWinner(_winner);
		if (_winner) {
			setFinished(true);
		}
	}, [finished, history, stepNumber, xIsNext]);
	const jumpTo = useCallback((step) => {
		const winner = calculateWinner([...history[step].squares]);
		setWinner(winner);
		setStepNumber(step);
		setXIsNext((step % 2) === 0);
		if (winner) {
			setFinished(true);
		} else {
			setFinished(false);
		}
	}, [history]);
	return (
		<GameContext.Provider
			value={{
				onClick: handleClick,
				jumpTo,
				history,
				stepNumber,
				winner,
				xIsNext,
			}}
		>
			{children}
		</GameContext.Provider>
	);
};

function calculateWinner(squares) {
	const lines = [
		[0, 1, 2],
		[3, 4, 5],
		[6, 7, 8],
		[0, 3, 6],
		[1, 4, 7],
		[2, 5, 8],
		[0, 4, 8],
		[2, 4, 6],
	];
	const length = lines.length;
	for (let i = 0; i < length; i++) {
		const [a, b, c] = lines[i];
		const player = squares[a];
		if (player && player === squares[b] && player === squares[c]) {
			return player;
		}
	}
	return null;
}

03 盤面のコンポーネントの関数をメモ化する

つぎに、盤面のコンポーネントモジュールsrc/components/Board.jsに、useCallbackフックを用います。マス目(Square)のJSX要素をつくって返す関数(renderSquare())と、それを3つ並べて1行のJSX要素にする関数(renderRow())です。とくに、前者は盤面のモジュールが、はじめに一度つくれば済みます(印を書き替えるのはマス目のコンポーネントの役目です)。したがって、依存配列は空[]にしました。書き直したモジュールの記述全体は、以下のコード002のとおりです。以下のサンプル001をCodeSandboxに公開しましたので、動きや各モジュールの具体的なコードについてはこちらでお確かめください。

src/components/Board.js

import { useCallback } from 'react';

const Board = () => {
	// const renderSquare = (i) =>
	const renderSquare = useCallback((i) => (
		<Square

		// />;
		/>
	), []);
	// const renderRow = (start) =>
	const renderRow = useCallback((start) => (
		<div className="board-row">

		{/* </div>; */}
		</div>
	), [renderSquare]);

};

コード002■useCallbackでコールバック関数をメモ化した盤面のコンポーネントモジュール

src/components/Board.js

import { useCallback } from 'react';
import Square from './Square';

const Board = () => {
	const renderSquare = useCallback((i) => (
		<Square
			id={i}
			key={i}
		/>
	), []);
	const renderRow = useCallback((start) => (
		<div className="board-row">
			{Array.from(Array(3), (_, index) => (
				renderSquare(start + index)
			))}
		</div>
	), [renderSquare]);
	return (
		<div>
			{renderRow(0)}
			{renderRow(3)}
			{renderRow(6)}
		</div>
	);
};

export default Board;

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

Create React App 入門


作成者: 野中文雄
作成日: 2020年02月08日


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