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

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

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

今回はチュートリアルで作成していた「三目並べ」の続き。
「タイムトラベル機能の追加 」を実装したので、その振り返りです。

ittech-nsnl.hatenablog.com

ja.reactjs.org

チュートリアルで作成できる三目並べは下記リンクから実際に遊べます。

https://codepen.io/gaearon/pen/gWWZgR?editors=0010


【目次】

1. Game基盤クラス

三目並べの盤面やゲームの進行状況を管理している、メインとなるクラス。

class Game extends React.Component {
  ・・・
}

1.1. コンストラクタ【constructor】

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

Gameコンポーネントの初期stateを3つ、コンストラクタ内でセットしています。
history
「nullで初期化されたサイズが9の配列squares」の配列。
三目並べの手番の履歴を管理する。

【stepNumber】
現在、何手目の状態なのかを管理する。

【xIsNext】
プレーヤーの手番を管理する。

1.2. マス目ボタンのクリックイベント【handleClick】

1.2.1. 最新の盤面情報を取得
  handleClick(i) {
    const history = this.state.history.slice(0, this.state.stepNumber + 1);
    const current = history[history.length - 1];
    const squares = current.squares.slice();
    ・・・
  }

history
history stateの配列の先頭から、「stepNumber state + 1」までの要素を取得。
例えば、stepNumber stateが "3"の場合、history state配列の1番目(インデックス "0")から、4(=3+1)番目(インデックス "3")までの要素を取得する。

【current】
上記で取得したhistoryから、最新の盤面情報を取得。

【squares】
現在の盤面の状態の各マス目を取得。

1.2.2. 盤面の情報を更新するか判定
    ・・・
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    ・・・
  }

勝敗がすでについている、もしくはクリックしたマスが既に埋まっている場合は何もせずにreturnする。

1.2.3. stateを更新
    ・・・
    squares[i] = this.state.xIsNext ? 'X' : 'O';
    this.setState({
      history: history.concat([{squares: squares,
      }]),
      stepNumber: history.length,
      xIsNext: !this.state.xIsNext,
    });
  }

最新のマス目情報「squares」のクリックされたマス目に、手番により"X" or "O"を代入。
「setState()」でコンポーネントのstateを更新し、画面を再描画する。

1.3. 過去の手番に戻る【jumpTo】

  jumpTo(step) {
    this.setState({
      stepNumber: step,
      xIsNext: (step % 2) === 0,
    });
  }

引数の「step」番目の手番まで、コンポーネントのstateを更新&画面の再描画を行う。

1.4. レンダー関数【render】

stateが更新されるたびにrender関数が呼ばれ、画面が再描画されます。

1.4.1. 現在の手番や勝敗を取得
  render() {
    const history = this.state.history;
    const current = history[this.state.stepNumber];
    const winner = calculateWinner(current.squares);
    ・・・
  }

history
history stateを取得しています。

【current 】
上記で取得した「this.state.history」のうち、現在の盤面情報を取得しています。

【winner 】
前回説明した「calculateWinner」関数を呼び出し、勝敗を取得します。

1.4.2. 過去の手番に戻ることのできるボタンを生成
  render() {
    ・・・
    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>
      );
    });
    ・・・
  }

moves は、過去の手番に戻る(ジャンプする)ことのできるボタン一覧を描画するためのReact要素。
mapメソッドを使ってhistory配列の各要素(手番)について、
* ボタンに表示させる文字列React要素「desc 」を生成 * リスト要素でボタンを1行描画するJSXを「move」へ返している。

「jumpTo()」は後程説明します。

【Reactのリストについて】

Reactでリストを描画する際は「key」を割り当てる必要があります。

Reactはリストが変更された場合、どこが変更されたのかを知る必要があります。
(仮想DOMにより差分のみを更新するため)
そのため、リストの各項目を区別できるように、keyプロパティを割り当ててあげます。

リストが再描画されるときは、Reactはリストの各項目のkeyについて、前回(再描画前)のリスト項目内に同じkeyを持つものがないか探します。

  • 前回リストに存在しないKeyが含まれていた場合
     ⇒コンポーネントを作成

  • 前回のリストにあったkeyが新しいリストに存在しない場合
     ⇒以前のコンポーネントを破棄

  • 前回リストのkeyとマッチするものが存在した場合
     ⇒対応するコンポーネントを移動

1.4.3. 現在の状況を判定
  render() {
    ・・・
    let status;
    if (winner) {
      status = 'Winner: ' + winner;
    } else {
      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
    }
    ・・・
  }

勝負がついているのか、ついていない場合は現在どちらの手番なのかを変数「status」に文字列として保持します。

1.4.4. JSXをreturnする
  render() {
    ・・・
    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>{moves}</ol>
        </div>
      </div>
    );
  }

「className="game-board"」のdiv要素では、Boardコンポーネントを描画します。
Boardコンポーネントには、現在の盤面の状況と、盤面のマス目ボタンが押されたときに呼び出されるメソッドをpropsとして渡しています。

「className="game-info"」div要素では、
「1.4.3. 現在の状況を判定」で作成した文字列変数「status」と、
「1.4.2. 過去の手番に戻ることのできるボタンを生成」で生成したReact要素「move」のJSXを、「ol」タグで囲むことで項番付きリストとしてボタンを描画しています。

2. 盤面クラス

三目並べの盤面を描画するクラス。

class Board extends React.Component {
    ・・・
}

2.1. マス目描画【renderSquare】

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

「Square」関数コンポーネントをreturnしています。
「Square」関数コンポーネントへは、Gameコンポーネントから渡されたprops内の最新の盤面情報のi番目のマス目と、onClickメソッド(GameコンポーネントのhandleClick()メソッド)をpropsとして渡しています。

2.2. レンダー関数【render】

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

盤面の各マス目を3×3になるように「renderSquare(i)」で描画しています。

3. マス目ボタンの関数コンポーネント

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

引数はBoardコンポーネントから渡されたpropsです。
盤面のマス目をボタンとして描画するためのJSXをreturnしています。

マス目ボタンがクリックされたときには、onClickイベントに指定されている「GameコンポーネントhandleClickメソッド」が呼び出されます。

4. まとめ

React公式のチュートリアルを一通り実施し、そのコードを備忘録的に解説しました。
自分の認識間違いなどあるかもしれませんが、まだ初心者のためご容赦ください(-_-メ)