🐣

Reactレクチャー3(三目並べ)

2021/10/07に公開

https://ja.reactjs.org/tutorial/tutorial.html#completing-the-game
https://codepen.io/gaearon/pen/gWWZgR

公式チュートリアルを参考に三目並べ(tictactoe)アプリを作成していきます。
完成図は以下のようになります。

ファイル構成と準備

コンポーネントベースのファイル構成を行う。チュートリアルでは、クラスコンポーネントが使われているが、今回はReactフックを用いて、関数コンポーネントで書き換えている。実際のコードは、今回使うコードは、以下を参考に。
https://github.com/nyaruo/react-lecture

ファイル構成

以下の3つのファイル(Board.js, Game.js, Square.js)を追加。

tictactoe-app
  ├── src
+ │   ├── components
+ │   │   ├── Board.js
+ │   │   ├── Game.js
+ │   │   └── Square.js
  │   ├── App.css 
  │   ├── App.js 
  │   ├── index.css 
  │   └── index.js 

全体コード

- App.css

全体コードより、CSS部分をコピーしてApp.cssにペースト(上書き)。cssとは、スタイル(デザイン)を表す部分であり、スタイルシートと呼ばれる。

App.css
body {
  font: 14px "Century Gothic", Futura, sans-serif;
  margin: 20px;
}

ol, ul {
  padding-left: 30px;
}

.board-row:after {
  clear: both;
  content: "";
  display: table;
}

.status {
  margin-bottom: 10px;
}

.square {
  background: #fff;
  border: 1px solid #999;
  float: left;
  font-size: 24px;
  font-weight: bold;
  line-height: 34px;
  height: 34px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
  width: 34px;
}

.square:focus {
  outline: none;
}

.kbd-navigation .square:focus {
  background: #ddd;
}

.game {
  display: flex;
  flex-direction: row;

}

.game-info {
  margin-left: 20px;
}

同様にして、Square.js, Board.js, Game.js も記述していく。

- Square.js

Square.jsは三目並べのマス目(正方形)部分。

Square.js
- function Square(props) {
-    return (
-        <button className="square" onClick={() => - props.onClick()}> 
-            {props.value}
-        </button>
-    );
    
    const Square = ({ onClick, value }) => {
    return (
      <button className="square" onClick={onClick}>
        {value}
      </button>
    );
  };
  
  export default Square;

className="square"で、App.cssの .squareスタイルを引用。
Board.jsから { onClick, value } = props を受け取っている.
onClickで、クリックした際に、props(親コンポーネントBoard.jsに記載)の処理を行う。props.value も同様にして、親コンポーネントから与えられたvalueを受け取る。

- Board.js

Board.jsは盤面部分。

Board.js
import Square from "./Square";

- class Board extends React.Component {
-  renderSquare(i) {
-    return (
-      <Square
-        value={this.props.squares[i]}
-        onClick={() => this.props.onClick(i)}
-      />
-    );
-  }

const Board = ({ squares, onClick }) => {
  const renderSquare = i => {
    return (
      <Square
        value={squares[i]}
        onClick={() => {
          onClick(i);
        }}
      />
    );
  };
  
-  render() {
    return (
      <>
        <div className="board-row">
          {renderSquare(0)}
          {renderSquare(1)}
          {renderSquare(2)}
        </div>
        <div className="board-row">
          {renderSquare(3)}
          {renderSquare(4)}
          {renderSquare(5)}
        </div>
        <div className="board-row">
          {renderSquare(6)}
          {renderSquare(7)}
          {renderSquare(8)}
        </div>
      </>
    );
-  }
}

export default Board;

import Square from "./Square";でSquare.js(三目並べのマス目部分)をimport。
<Square value={squares[i]} onClick={() => {onClick(i);}}/>で呼び出し。

Game.jsから { squares, onClick } = props を受け取っている。
Squareコンポーネントを9つ記述することで、盤面を作成。

- Game.js

子コンポーネント(Square.js, Board.js)をまとめた親コンポーネント。
アプリの全体部分。

Game.js
import { useState } from "react";
import Board from "./Board";

const Game = () => {
- class Game extends React.Component {
-  constructor(props) {
-    super(props);
-    this.state = {
     const [history, setHistory] = useState([
       {
          squares: Array(9).fill(null)
        }
     ]);
-      history: [
-        {
-          squares: Array(9).fill(null)
-        }
-      ],
       const [stepNumber, setStepNumber] = useState(0);
-      stepNumber: 0,
       const [xIsNext, setXIsNext] = useState(true);
-      xIsNext: true
-    };
-  }

   const handleClick = i => {
-  handleClick(i) {
     const historyCurrent = history.slice(0, stepNumber + 1);
-    const history = this.state.history.slice(0, this.state.stepNumber + 1);
     const current = historyCurrent[historyCurrent.length - 1];
-    const current = history[history.length - 1];
     const squares = [...current.squares];
-    const squares = current.squares.slice();
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
     squares[i] = xIsNext ? "X" : "O";
-    squares[i] = this.state.xIsNext ? "X" : "O";
-    this.setState({
-      history: history.concat([
-        {
-          squares: squares
-        }
-      ]),
      setHistory([...historyCurrent, { squares }]);
      setStepNumber(historyCurrent.length);
      setXIsNext(!xIsNext);
    };
-      stepNumber: history.length,
-      xIsNext: !this.state.xIsNext
-    });
-  }

    const jumpTo = step => {
      setStepNumber(step);
      setXIsNext(step % 2 === 0);
    };
-  jumpTo(step) {
-    this.setState({
-      stepNumber: step,
-      xIsNext: (step % 2) === 0
-    });
-  }

-  render() {
     const current = history[stepNumber];
-    const history = this.state.history;
-    const current = history[this.state.stepNumber];
    const winner = calculateWinner(current.squares);

    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>
	  <button onClick={() => jumpTo(move)}>{desc}</button>
        </li>
      );
    });

    let status;
    if (winner) {
      status = "Winner: " + winner;
    } else {
       status = `Next Player : ${xIsNext ? "X" : "O"}`;
-      status = "Next player: " + (this.state.xIsNext ? "X" : "O");
    }

    return (
      <div className="game">
        <div className="game-board">
	   <Board 
	     squares={current.squares} 
	     onClick={i => handleClick(i)} 
	   />
-          <Board
-            squares={current.squares}
-            onClick={i => this.handleClick(i)}
-          />
        </div>
        <div className="game-info">
          <div>{status}</div>
          <ol>{moves}</ol>
        </div>
      </div>
    );
-  }
};

const 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;
};

export default Game;

const [history, setHistory] = useState([
         {squares: Array(9).fill(null)}
]);
useState()を使って、関数コンポーネントに状態を持たせる
const [state変数, set関数] = useState(初期値)
<Board /> でOnClickでhandleClick(i)を呼び出し。

そして、App.jsを以下のように書き換える。

App.js
import './App.css';
import Game from './components/Game'

function App() {
  return (
    <Game />
  );
}

export default App;

改めて、今回用いた全体コードはこちらに。
https://github.com/nyaruo/react-lecture

時間があれば復習の意味も込めて、GitHubにアップロードしてみよう。
https://qiita.com/under_chilchil/items/ec9d0050c1e3fb6576de

GitHubで編集を提案

Discussion