ITエンジニア日記 ~NO SKILL, NO LIFE~

学んだ技術や、気になることをアウトプットしていきます。プログラミング, インフラ, etc...

【React】チュートリアル その1

2回目のReactの勉強会に参加してきました。今回はもくもく会だったのですが、そこでReactの公式チュートリアルを進めてきました。
チュートリアルでは「三目並べ」を作っていきます。

今回は「チュートリアルの準備 」から「ゲーム勝者の判定 」まで進めたので自分の振り返り用にまとめます。

ja.reactjs.org

※今回のアプリで使用しているCSSは、チュートリアルで用意されているものを使用しています。CSSについての解説はしていません。

【目次】

1. 盤面のClass Component

盤面クラスコンポーネントのソースは次の通り。

class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(null),
      xIsNext: true,
    };
  }

  /**
   * マス目のクリックイベント.
   * @param {*} i マス目のインデックス番号
   */
  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,
    });
  }

  /**
   * マス目描画.
   * @param {*} i マス目のインデックス番号
   */
  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>
    );
  }
}

1.1. コンストラク

  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(null),
      xIsNext: true,
    };
  }

コンストラクタでは、stateとして

  • squares : 3 x 3 の盤面の状態を保持する
  • xIsNext : プレーヤーの手番を管理。盤面では"X"と"O"を交互に指していく。

という2つの状態を宣言&初期化。

1.2. マス目のクリックイベント【handleClick(i)】

handleClick(i)

盤面のマス目がクリックされたときに呼び出されるメソッド。
引数はクリックされたマス目の、盤面上のインデックス番号。

const squares = this.state.squares.slice();
if (calculateWinner(squares) || squares[i]) {
    return;

calculateWinner関数に盤面の状態を渡して決着がついているかを判定。決着がすでについている、もしくはクリックされたマス目がすでに埋まっている場合は何もせずにreturnしている。

squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
    squares: squares,
    xIsNext: !this.state.xIsNext,
});

this.state.xIsNext ? 'X' : 'O'」で現在のプレーヤーの手番によってマス目に表示させる記号を判定している。その記号を、squares配列のクリックされたインデックス番号へ代入している。
最後に、this.setState()でstateの状態を更新している。

1.3. マス目描画メソッド【renderSquare(i) 】

 renderSquare(i) 

マス目を描画するメソッド。

return (
    <Square
    value={this.state.squares[i]}
    onClick={() => this.handleClick(i)}
    />
);

Square関数コンポーネント
・マス目の状態「this.state.squares[i]」("X" or "O" or null)をvalueという名称で、
・マス目がクリックされたときに呼び出すメソッド「handleClick」をonClickという名称で
引数として渡している。
描画を実際にするのはSquare関数コンポーネント(2. で説明)。

1.4. render()メソッド

const winner = calculateWinner(this.state.squares);
let status;
if (winner) {
    status = 'Winner:' + winner;
} else {
    status = 'Next player:' + (this.state.xIsNext ? 'X' : 'O');
}

calculateWinner関数にstateのsquaresを引数として渡して、勝負がついているかを判定。
勝負がついているかによって、描画する文字列(status)を変えている。

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

上記で作成した文字列statusの描画と、盤面の各マス目をrenderSquareメソッドで描画をしている。

2. マス目の関数コンポーネント

マス目の関数コンポーネントのコードは次の通り。
関数コンポーネントは、renderメソッドのみを有して自分のstateを持たないコンポーネントのこと。

function Square(props) {
  return (
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  );
}

盤面のマス目をボタンとして描画している。
マス目(ボタン)が押されたとき、引数のpropsが持っている盤面クラスコンポーネントrenderSquareメソッドが呼び出される。
(1.2. マス目描画メソッド【renderSquare(i) 】を参照)

{props.value}で、マス目に"X"を描画 もしくは "O"を描画 もしくは描画しない(null)をしている。

3. 勝負判定用のヘルパー関数

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

勝負がついたかを判定するためのヘルパー関数。
linesで勝負がついた時のパターンを定義している。
(横一列が3パターン、縦一列が3パターン、斜めが2パターンの計8パターン)

linesの各パターンごとに、引数のsquares(盤面の状態)を判定。「a」「b」「c」がすべて"X" もしくは すべて"O"のときに勝負がついたと判定している。

4. まとめ

Reactのチュートリアルについて、書いたコードの解説をしました。
自分の備忘録的なものなので、わかりにくいかもしれません。
次は「タイムトラベル機能の追加 」をチュートリアルで進めたら、そのコード解説を書こうと思います。