React のチュートリアル(9)
※ これは 2023/05/18 時点の create-react-app@5.0.1 の情報です
最新版では動作が異なる可能性がありますのでご注意ください
前回に引き続き React のチュートリアルの続きを焦らず少しずつ進める
前回勝敗を表示するようにしたら、次は巻き戻しできるようにするらしい
前回の index.js
の下記、Board
と Game
に
class Board extends React.Component { constructor(props) { super(props); this.state = { squares: Array(9).fill(null), xIsNext: true, }; } handleClick(i) { const squares = this.state.squares.slice(); if (calculateWinner(squares) || squares[i]) { return; } squares[i] = this.state.xIsNext ? 'X' : 'O'; this.setState({ squares: squares, xIsNext: !this.state.xIsNext, }); } renderSquare(i) { return ( <Square value={this.state.squares[i]} // <-- onClick={() => this.handleClick(i)} // <-- /> ); } render() { const winner = calculateWinner(this.state.squares); // <-- let status; // <-- if (winner) { // <-- status = 'Winner: ' + winner; // <-- } else { // <-- status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); // <-- } // <-- return ( <div> <div className="status">{status}</div> <div className="board-row"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div> <div className="board-row"> {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)} </div> <div className="board-row"> {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)} </div> </div> ); } } class Game extends React.Component { render() { return ( <div className="game"> <div className="game-board"> <Board /> </div> <div className="game-info"> <div>{/* status */}</div> <ol>{/* TODO */}</ol> </div> </div> ); } }
履歴情報を管理し、結果表示を Board
から Game
に移動する下記のようなコードに変更
class Board extends React.Component { handleClick(i) { const squares = this.state.squares.slice(); if (calculateWinner(squares) || squares[i]) { return; } squares[i] = this.state.xIsNext ? 'X' : 'O'; this.setState({ squares: squares, xIsNext: !this.state.xIsNext, }); } renderSquare(i) { return ( <Square value={this.props.squares[i]} // <-- onClick={() => this.props.onClick(i)} // <-- /> ); } render() { return ( <div> <div className="board-row"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div> <div className="board-row"> {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)} </div> <div className="board-row"> {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)} </div> </div> ); } } class Game extends React.Component { constructor(props) { // <-- super(props); // <-- this.state = { // <-- history: [{ // <-- squares: Array(9).fill(null), // <-- }], // <-- xIsNext: true, // <-- }; // <-- } // <-- render() { const history = this.state.history; // <-- const current = history[history.length - 1]; // <-- const winner = calculateWinner(current.squares); // <-- let status; // <-- if (winner) { // <-- status = 'Winner: ' + winner; // <-- } else { // <-- status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); // <-- } // <-- return ( <div className="game"> <div className="game-board"> <Board /> </div> <div className="game-info"> <div>{/* status */}</div> <ol>{/* TODO */}</ol> </div> </div> ); } }
次に Board
にあった handleClick
関数に下記のように改変して Game
に移動
class Game extends React.Component { constructor(props) { super(props); this.state = { history: [{ squares: Array(9).fill(null), }], xIsNext: true, }; } handleClick(i) { // <-- const history = this.state.history; // <-- const current = history[history.length - 1]; // <-- const squares = current.squares.slice(); // <-- if (calculateWinner(squares) || squares[i]) { // <-- return; // <-- } // <-- squares[i] = this.state.xIsNext ? 'X' : 'O'; // <-- this.setState({ // <-- history: history.concat([{ // <-- squares: squares, // <-- }]), // <-- xIsNext: !this.state.xIsNext, // <-- }); // <-- } // <-- render() { const history = this.state.history; const current = history[history.length - 1]; const winner = calculateWinner(current.squares); let status; if (winner) { status = 'Winner: ' + winner; } else { status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); } return ( <div className="game"> <div className="game-board"> return ( <div className="game"> <div className="game-board"> <Board squares={current.squares} // <-- onClick={(i) => this.handleClick(i)} // <-- /> </div> <div className="game-info"> <div>{status}</div> // <-- <ol>{/* TODO */}</ol> </div> </div> );
Game
にいろいろ移してきたこの時点で全体のコードはこんな感じ
import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; function Square(props) { return ( <button className="square" onClick={props.onClick}> {props.value} </button> ); } class Board extends React.Component { renderSquare(i) { return ( <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)} /> ); } render() { return ( <div> <div className="board-row"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div> <div className="board-row"> {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)} </div> <div className="board-row"> {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)} </div> </div> ); } } class Game extends React.Component { constructor(props) { super(props); this.state = { history: [{ squares: Array(9).fill(null), }], xIsNext: true, }; } handleClick(i) { const history = this.state.history; const current = history[history.length - 1]; const squares = current.squares.slice(); if (calculateWinner(squares) || squares[i]) { return; } squares[i] = this.state.xIsNext ? 'X' : 'O'; this.setState({ history: history.concat([{ squares: squares, }]), xIsNext: !this.state.xIsNext, }); } render() { const history = this.state.history; const current = history[history.length - 1]; const winner = calculateWinner(current.squares); let status; if (winner) { status = 'Winner: ' + winner; } else { status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); } return ( <div className="game"> <div className="game-board"> <Board squares={current.squares} onClick={(i) => this.handleClick(i)} /> </div> <div className="game-info"> <div>{status}</div> <ol>{/* TODO */}</ol> </div> </div> ); } } // ======================================== const root = ReactDOM.createRoot(document.getElementById("root")); root.render(<Game />); 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], ]; for (let i = 0; i < lines.length; i++) { const [a, b, c] = lines[i]; if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { return squares[a]; } } return null; }
これを実行、VSCode のターミナルから下記コマンドを実行してブラウザで開く
npm start
履歴を持つように結構変えたけどリファクタリングで動作は変わらない