Reactチュートリアル徹底解説！

# はじめに
[React公式チュートリアル](https://ja.reactjs.org/tutorial/tutorial.html)を完了したので、アウトプット＆初学者の方に向けた解説を目的として記事にしました。
実際の手順に関しては公式に細かく記載されているため、**本記事では実装手順を省き、内部の動きについて**かなり細かく噛み砕いて解説しました。
対象としては、

- 何となくは理解できたけど細かい所が不安・・・
- とりあえずチュートリアルを終わらせたけど何が起こっているのかよく分からん
- そもそもJavaScriptが分からねえ

上記のような方々を対象としています。
少し長めの記事ですが、本記事を読めば**公式チュートリアルの内容をほぼ理解できる**ような内容になっているかと思いますので、最後までどうかお付き合い下さい。
# Squareコンポーネント
ここでは、三目並べのマス目部分を担当しているSquareコンポーネントについて解説します。

```jsx

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573865/34cf73ed-09e8-b7d9-440a-4ad6d58db201.png)

とは言っても、非常に単純な構成のため1点のみです。

### 1. クラスコンポーネントではなく関数コンポーネントを使用
チュートリアルのコンポーネント構成は、上から`Game > Borad > Square`となっており、stateは全てGameコンポーネントで管理されています。
そのため、単に要素をreturnするだけのSquareコンポーネントは、よりシンプルに記載できる関数コンポーネントで実装されています。

# Boardコンポーネント
Boardコンポーネントは、Squareのまとまりを管理しています。
render内に記載されている9つの`renderSquare`関数によって、Boradコンポーネント内にSquareコンポーネントを配置しています。

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

先程と比べると少しだけコード量が増えてきましたね。
しかしここでは`renderSquare`関数を押さえれば意味が理解できると思います。

### 1. `Square value`
`{this.props.squares[i]}`の`squares`は、Gameコンポーネントで管理されている配列のstateです。
後述しますが、`squares`は9つのマス目全てに関して、どこにどちらのマーク(☓か○か）が入っているのか情報を持っています。
引数として渡す数値をもとに、マス目の場所に応じたマークを呼び出し、Square毎のvalueに代入しています。

### 2. `Square onClick`
ここでは、Gameコンポーネント内の`handleClick`関数を呼び出すための処理が記載されています。
Gameコンポーネントのrender部分は以下です。

```jsx
<Board
  squares={current.squares}
  onClick={(i) => this.handleClick(i)}
/>
```

Boardコンポーネント内の`onClick={() => this.props.onClick(i)}`によって、配置を表す引数を渡しつつ`handleClick`関数を呼び出しています。
この記述をもとに、マス目毎に`handleClick`イベントを発動させることができるようになっています。  
### 3. `onClick`内の`() =>`
`onClick={() => this.props.onClick(i)}`の中に記載されているアロー関数は何のためにあるのか？
答えはクラスメソッドをバインドするためです。

> JSX のコールバックにおける this の意味に注意しなければなりません。JavaScript では、クラスのメソッドはデフォルトではバインドされません。this.handleClick へのバインドを忘れて onClick に渡した場合、実際に関数が呼ばれた時に this は undefined となってしまいます。
[公式ドキュメント：イベント処理](https://ja.reactjs.org/docs/handling-events.html)

つまり、今回のように末尾に`()`を付けずに関数を呼び出す際は、何かしらの形でバインドしなければなりません。
別の方法としては、Gameコンポーネントのコンストラクタ内に以下のように記載することで解決できます。

```jsx
this.handleClick = this.handleClick.bind(this);
```

# calculateWinnerコンポーネント
Gameコンポーネントに進む前に、まずは「勝利条件が成立しているか」を判定するcalculateWinnerコンポーネントについて、ほぼReactに関係ない部分ですが説明しておきます。

```jsx
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;
}
```

### 1. `lines`定数
蛇足ですが、三目並べの勝利条件は「縦・横・斜めのいずれかに同じマークが3つ並ぶこと」です。
後述するfor文で、`lines`内の組み合わせ全てに、同じマークがあるかどうかを判定していきます。
ハードコーディングされていますが、今回は3マス×3マスかつ三目並べ専用なので問題はありません。

### 2. for文
まず`const [a, b, c] = lines[i]`で、`lines`内の組み合わせを分割代入しています。
例えば`lines[2]`であれば、`a, b, c`はそれぞれ`6, 7, 8`が代入されます。
それらを9つのマス目全ての情報を持っている`squares`配列のインデックスとして使用し、if文内で総当たりの条件判定を行っている形です。

# Gameコンポーネント
いよいよ本題のGameコンポーネントです。大まかに機能を分けると、
　1. `state`
　2. `handleClick`
　3. `jumpTo`
　4. `render`
このあたりでしょうか。順を追って解説していきます。

<details><summary>コード全体</summary><div>

```jsx
class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      history: [{
        squares: Array(9).fill(null),
      }],
      stepNumber: 0,
      xIsNext: true,
    };
  }

  handleClick(i) {
    const history = this.state.history.slice(0, this.state.stepNumber + 1);
    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,
      }]),
      stepNumber: history.length,
      xIsNext: !this.state.xIsNext,
    });
  }

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

  render() {
    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>
        </li>
      );
    });

    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>{moves}</ol>
        </div>
      </div>
    );
  }
}

// ========================================

ReactDOM.render(
  <Game />,
  document.getElementById('root')
);
```

</div></details>

### 1. state

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

1. `history`
ここでは`history`内部に配列の`squares`がセットされています。
まず`Array(9).fill(null)`は「全てがnullである、サイズが9の配列」です。
この記述によりstateの初期値として、一切マークされていない、ゲームスタート時のボードがセットされます。
次に直接`suqares`を保持するのではなく、わざわざ`history`内部に`squares`を保持している理由ですが、これは後で過去の着手を表示するためです。
後述しますが、ゲームが進行する度に1つずつ`value`が追加された`squares`が増えていくイメージ。
![スクリーンショット 2020-12-07 23.30.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573865/069fa244-cbde-e6c1-91e1-e6ad673d6819.png)
 
2. `stepNumber`
render内に`const current = history[this.state.stepNumber];`と記載されている通り、ゲームの手番を管理するためのstateです。
`history[]`にインデックスを渡すことで、現在の`squares`の状態を`current`に代入しています。

3. `xIsNext`
こちらは次の手番が○か☓かを管理するためのstateです。
同じくrender内に`status = 'Next Player: ' + (this.state.xIsNext ? 'X' : 'O');`と記載されています。
見慣れない方も居るかもしれませんが、こちらは条件（三項）演算子と呼ばれる記法で、`?`までが条件文、`:`の左がtrue、右がfalse時の処理を表しています。
今回の例だと`this.state.xIsNext`がtrueならX、falseなら○と、より簡潔に記述できますね。

### 2. handleClick
ここではイベント発火元Squareのインデックスを引数として、squareがクリックされた時の処理について解説します。


```jsx
  handleClick(i) {
    const history = this.state.history.slice(0, this.state.stepNumber + 1);
    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,
      }]),
      stepNumber: history.length,
      xIsNext: !this.state.xIsNext,
    });
  }
```

1. 定数
 - `history`  
現在の`history`をsliceメソッドで「現在の手番 + 1」で作り直し、`history`に代入しています。
ぱっと見は何のための処理か分からなくなりそうですが、これは後述の`jumpTo`メソッドが実行された時に必要な処理です。
`jumpTo`メソッド内では`history`をsetStateしないので、メソッド実行後も`history`の中身は変わりません。
しかし、このままでは`history`がおかしくなるため、`handleClick`メソッド実行時に正しい中身に再設定している形です。
以下は前回画像の状況から過去の手番に戻った場面ですが、盤面と`history`の数が一致していません。
空のマス目をクリックすることで`historyが作り直され、正しい履歴に戻ります。
![スクリーンショット 2020-12-08 0.15.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573865/36cc0d30-0864-7fed-68f9-27f5c94f3f18.png)
 - `current`  
こちらは単に現在の`history`のみを取得・代入しています。
 - `squares`  
`cconst squares = current.squares.slice()`と繋げることで、盤面を更新する前に、現在の状態を`squares`に代入しています。

2. 処理
 - if文  
ここでは「ゲームの勝者が居る場合」 or 「マーク済のマスがクリックされた場合」に早期returnするためにif文が使用されています。  
`if (calculateWinner(squares) || squares[i]) `となっているので、どちらかがtrueであればこの先の処理は行われません。
 - 条件演算子  
先程のような構文が再び出てきましたね。ここでは`xIsNext`がtrueなら☓、falseなら○がクリックされたsquareのvalueとして代入されます。

3. setState
 - `history`  
少し前で`history`を作り直した意味がやっとここで出てきます。  
`history.concat([{squares: squares,}])`とすることで、今までの履歴に今回作成された`squares`を追加できます。  
この一連の処理によって、今までの履歴を管理しつつ正しい順番で今回の処理結果を追加することができました。  
 - `stepNumber`  
どうせ使用時に+ 1するので単に`history.length`を代入。
 - `xIsNext`  
`!this.state.xIsNext`とする事で値を反転させています。  
`xIsNext`は真偽値なので、この処理の度にtrue / false切り替わる形です。

### 3. jumpTo

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

- `stepNumber`  
引数の`step`にはターゲットとなる`history`のインデックスが入るため、`stepNumber`に正しい値がセットされます。
- `xIsNext`  
`xIsNext: (step % 2) === 0`とすることで`xIsNext`に正しい値がセットされます。

### 4. render
いよいよ大詰めです。少し長いですが頑張りましょう！

```jsx
  render() {
    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>
        </li>
      );
    });

    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>{moves}</ol>
        </div>
      </div>
    );
  }
```

1. 定数
 - `const history = this.state.history`  
 現時点での全ての`history`をセット
 - `const current = history[this.state.stepNumber]`  
 最新の盤面をセット
 - `const winner = calculateWinner(current.squares)`  
 現在の盤面での勝者をセット
 - `const moves = history.map((step, move) => {}`  
 まず、この処理内で使われている`desc`は「descending order」ではなく「description」です。（蛇足かもしれませんが、初見で勘違いしたので・・・）  
ここでは`history`それぞれのインデックスを取得するためにmap処理を行っています。  
なので「'step' が宣言されていますが、その値が読み取られることはありません。」と表示されていますが問題ありません。<br><br>
`const desc = move ? 'Go to move #' + move : 'Go to game start';`  
ここでまたもや条件演算子の登場です。  
`move`がtrue、つまり「0やnull以外の値」であれば`desc`に`'Go to move #' + move'`を代入、`move`がfalseであれば`'Go to game start'`を行います。  
`move`がfalse（今回だと0のみ）の場合は`history[0]`＝ゲームスタート時の盤面なので`'Go to game start'`と表示させる寸法です。

2. let status
空の`status`を宣言しておき、if文内で`winner`の有無に応じて表示を出し分けています。
ここは非常にシンプルですね。

3. return
ここではあまり説明することもありませんが1点だけ。
`onClick={(i) => this.handleClick(i)}`でアロー関数の引数になっている`i`ですが、これはBoardコンポーネント内の`renderSquare(i)`から来ています。
`Square value`に渡しているインデックスをついでに利用した形です。

あとは最後に`ReactDOM.render`して終了です！

# おわりに
ここまでお付き合い下さりありがとうございました。
本記事が少しでも皆様のお役に立てれば幸いです。
ほぼ初めての記事投稿だったのですが、作成にあたり自分の中でも理解が深まったと感じます。
散々言われている事ですが、やはり自身で言語化 → アウトプットする事は大切だなーとしみじみ・・・
これを機にアウトプットをしっかり癖付けていきます。

、、、とはいえ今年は終わりが近いので来年から頑張ることにします。
それでは皆さま良いお年を。