HTML5テクニカルノート
Create React App 入門 05: 無駄な処理を省く
- ID: FN2002001
- Technique: ECMAScript 2015
- Library: React 16.12.0
Create React Appのひな形からチュートリアルと同じマルバツゲームをつくる「Create React App 入門」シリーズ最終回は、無駄な処理を省く断捨離です。細かな修正だとしても、無駄はなくした方がよいでしょう
01 引き分けたら終わりにする
前回src/components/App.js
モジュールで、勝ちが決まったら、マス目をクリックしてもゲームの処理が先に進まないようにしました。引き分けの場合も同じです。つまり、指し手(stepNumber
)が9に達したら、ゲームは終わりにすべきでしょう。
src/components/App.jsclass 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.jsconst 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.jsclass 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.jsconst 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を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;
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 入門
- Create React App 入門 01: 3×3のマス目をつくる
- Create React App 入門 02: クリックしたマス目にXをつける
- Create React App 入門 03: マルバツで勝ち負けを決める
- Create React App 入門 04: ゲームの履歴をさかのぼる
- Create React App 入門 05: 無駄な処理を省く
作成者: 野中文雄
作成日: 2020年02月01日
Copyright © 2001-2020 Fumio Nonaka. All rights reserved.