サイトトップ

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

HTML5テクニカルノート

Create React App 入門 05: 無駄な処理を省く


Create React Appのひな形からチュートリアルと同じマルバツゲームをつくる「Create React App 入門」シリーズ最終回は、無駄な処理を省く断捨離です。細かな修正だとしても、無駄はなくした方がよいでしょう

01 引き分けたら終わりにする

前回src/components/App.jsモジュールで、勝ちが決まったら、マス目をクリックしてもゲームの処理が先に進まないようにしました。引き分けの場合も同じです。つまり、指し手(stepNumber)が9に達したら、ゲームは終わりにすべきでしょう。

src/components/App.js

class App extends React.Component {

	handleClick(i) {

		if (this.state.stepNumber >= 9) {
			this.setState({finished: true});
			return;
		}

	};

}

02 繰り返すテンプレート要素はループ処理でつくる

もうひとつ気になるのは、src/components/Board.jsモジュールのマス目(Square)を3つずつテンプレートに並べるコードです。for文の繰り返し処理にした方が、見た目はすっきりします。要素を並べて返す関数(renderRow())は新たに定めました。繰り返し処理で加えるテンプレート要素に、keyプロパティを加えることは忘れないでください。

src/components/Board.js

const Board = (props) => {
	const renderSquare = (i) =>
		<Square
			key={i}

		/>
	const renderRow = (start, end) => {
		const rowSquares = [];
		for (let i = start; i <= end; i++) {
			rowSquares.push(renderSquare(i));
		}
		return rowSquares;
	}
	return (
		<div>
			<div className="board-row">
				{/* {renderSquare(0)}
				{renderSquare(1)}
				{renderSquare(2)} */}
				{renderRow(0, 2)}
			</div>
			<div className="board-row">
				{/* {renderSquare(3)}
				{renderSquare(4)}
				{renderSquare(5)} */}
				{renderRow(3, 5)}
			</div>
			<div className="board-row">
				{/* {renderSquare(6)}
				{renderSquare(7)}
				{renderSquare(8)} */}
				{renderRow(6, 8)}
			</div>
		</div>
	);
};

03 ゲームが終わったらonClickハンドラをなくす

ゲームの勝ちが決まるか、引き分けに終わったとき、src/components/App.jsモジュールのonClickハンドラのメソッド(handleClick())は処理を直ちに終えるようになりました。さらにいえば、メソッドを呼び出すことそのものが無駄です。ゲームのけりがついたら、onClickハンドラを除いてしまいましょう。

ユーザーのクリックを受けるonClickハンドラは、Boardコンポーネントに定めてありました。そこで、src/components/App.jsモジュールのテンプレートで、ゲーム終了のプロパティ(finished)を渡しておきます。

src/components/App.js

class App extends React.Component {

	render() {

		return (
			<div className="game">
				<Board

					finished={this.state.finished}

				/>

			</div>
		);
	}
}

そして、モジュールsrc/components/Board.jsで手を加えるのは、マス目(Square)のテンプレートをつくって返す関数(renderSquare())です。ゲーム終了のプロパティ(finished)値によりonClickハンドラを加えるかどうか切り分けます。

src/components/Board.js

const Board = (props) => {
	const renderSquare = (i) =>
		(props.finished) ?
			<Square
				key={i}
				value={props.squares[i]}
			/> :
			<Square
				key={i}
				value={props.squares[i]}
				onClick={() => props.onClick(i)}
			/>

};

ゲームの決着がつけば、onClickハンドラ自体が除かれます。無駄なメソッド呼び出しがなくなったということです。もちろん、履歴をさかのぼれば、またクリックで手が進められます。つまり、ゲームの動きそのものは変わりません(図001)。

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

図001

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

コード001■断捨離したふたつのモジュール

src/components/App.js

import React from 'react';
import Board from './Board';
import './App.css';

class App extends React.Component {
	constructor(props) {
		super(props);
		this.state = {
			history: [
				{squares: new Array(9)}
			],
			stepNumber: 0,
			xIsNext: true,
			finished: false
		};
		this.handleClick = this.handleClick.bind(this);
	}
	handleClick(i) {
		if (this.state.finished) { return; }
		if (this.state.stepNumber >= 9) {
			this.setState({finished: true});
			return;
		}
		const history = this.state.history.slice(0, this.state.stepNumber + 1);
		const squares = [...history[history.length - 1].squares];
		console.log('history:', history.length, this.state.stepNumber);
		if (squares[i]) { return; }
		const winner = calculateWinner(squares);
		if (winner) {
			this.setState({finished: true});
			return;
		}
		squares[i] = this.state.xIsNext ? 'X' : 'O';
		this.setState({
			history: [...history, {squares}],
			stepNumber: history.length,
			xIsNext: !this.state.xIsNext
		});
	};
	jumpTo(step) {
		this.setState({
			stepNumber: step,
			xIsNext: (step % 2) === 0,
			finished: false
		});
	}
	render() {
		const history = [...this.state.history];
		const squares = [...history[this.state.stepNumber].squares];
		const winner = calculateWinner(squares);
		const status = (winner) ?
			'Winner: ' + winner :
			'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
		const moves = history.map((step, move) => {
			const desc = move ?
				'Go to move #' + move :
				'Go to game start';
			return (
				<li key={move}>
					<button onClick={() => this.jumpTo(move)}>{desc}</button>
				</li>
			);
		});
		return (
			<div className="game">
				<Board
					squares={squares}
					finished={this.state.finished}
					onClick={(i) => this.handleClick(i)}
				/>
				<div className="game-info">
					<div>{status}</div>
					<ol>{moves}</ol>
				</div>
			</div>
		);
	}
}
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;
}

export default App;

src/components/Board.js

import React from 'react';
import Square from './Square';

const Board = (props) => {
	const renderSquare = (i) =>
		(props.finished) ?
			<Square
				key={i}
				value={props.squares[i]}
			/> :
			<Square
				key={i}
				value={props.squares[i]}
				onClick={() => props.onClick(i)}
			/>
	const renderRow = (start, end) => {
		const rowSquares = [];
		for (let i = start; i <= end; i++) {
			rowSquares.push(renderSquare(i));
		}
		return rowSquares;
	}
	return (
		<div>
			<div className="board-row">
				{renderRow(0, 2)}
			</div>
			<div className="board-row">
				{renderRow(3, 5)}
			</div>
			<div className="board-row">
				{renderRow(6, 8)}
			</div>
		</div>
	);
};

export default Board;

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

Create React App 入門


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


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