【React】チュートリアルの三目並べをやる #1

[前回](https://qiita.com/GodPhwng/items/281be00fa9ff3fec1fdd)まではProgateの無料レッスンを一通りこなしました。

今回からはアプリを作成するアウトプットを行っていこうと思います。
または、自分が新たに習得した知識の共有等も行えたら良いなと思います。

**次回**
[【React】チュートリアルの三目並べをやる #2](https://qiita.com/GodPhwng/items/1a4c2e0b784c7a4f8b54)

#React公式チュートリアル
Reactの[公式チュートリアル](https://ja.reactjs.org/tutorial/tutorial.html)に三目並べ~~ゲーム~~があり、なぜかこれが気になってしまうので、作っていきたいと思います。

最終成果物は　→　[三目並べ~~ゲーム~~](https://codepen.io/gaearon/pen/gWWZgR?editors=0010)

これをチュートリアルに沿って作るのが目標です。

##前提知識
アロー関数、クラス、`let`および`const`が理解できていることが前提らしい。
完璧には理解できていないが、わからなければググるので問題なし。

##チュートリアルの準備
開発環境は以下の2つ

 - ブラウザで書く
 - ローカルに開発環境を構築して書く
 
以前Progateの無料レッスンをやっているので、ローカルに構築済みです。
[Progate無料版をやってみる【React】](https://qiita.com/GodPhwng/items/4657567ec11f56fda40c)

以下のソースコードを元に作成する模様。
https://codepen.io/gaearon/pen/oWWQNa?editors=0010

イメージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/d3a9651d-bc18-c53b-1c3a-dfcb93404606.png)


3つのコンポーネントで構成される

 - Square：　マス目を表し、buttonタグをレンダー
 - Board：　9個Squareをreturnしている。マス目全体
 - Game：　あれ、これ何を表しているんだ？
 　チュートリアルには「後ほど埋めることになるプレースホルダーを描画しています」とのこと。後ほど書くのね。

とりあえず元となるソースをコピペまたは模写していきます。
index.jsを変更しちゃいます。

```react:index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import * as serviceWorker from './serviceWorker';

class Square extends React.Component {
  render() {
    return <button className="square">{/* TODO */}</button>;
  }
}

class Board extends React.Component {
  renderSquare(i) {
    return <Square />;
  }

  render() {
    const status = 'Next palayer: X';

    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="gmae-board">
          <Board />
        </div>
        <div className="game-info">
          <div>{/* status */}</div>
          <div>{/* TODO */}</div>
        </div>
      </div>
    );
  }
}

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

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
```
一番下のserviceWorker.unregister();を消すとエラーになっていた。
以前のインストール時のHello World的なチュートリアルで入ってしまった模様。
キャッシュらしいです。よくわからない・・・。
[参考にさせていただきました。](https://medium.com/@gipcompany/create-react-app%E3%81%AB%E3%82%88%E3%82%8A%E4%BD%9C%E6%88%90%E3%81%95%E3%82%8C%E3%82%8B%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AB%E5%90%8C%E6%A2%B1%E3%81%95%E3%82%8C%E3%82%8Bserviceworker-js%E3%81%A3%E3%81%A6%E4%BD%95-unregister-%E3%81%A3%E3%81%A6%E4%BD%95%E3%82%92%E3%81%97%E3%81%A6%E3%81%84%E3%82%8B%E3%81%AE-b9affce42ecb)

```css:index.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;
}
```

###Propsでデータを渡す
Props(properties)を用いて親から子に値を受け渡す。
Board(親)からSquare(子)に値を受け渡します。
渡す値は、Board.renderSquareの引数`i`です。

```react:Boad側
class Board extends React.Component {
  renderSquare(i) {
    return <Square value={i} />;
  }
```

```react:Square側
class Square extends React.Component {
  render() {
    return <button className="square">{this.props.value}</button>;
  }
}
```
プロジェクトトップに移動し(`cd 移動先`)

```
npm start
```
します。

そして`http://localhost:3000`にアクセスします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/0ecaac3a-f55f-e191-459d-df6aa3b8f727.png)
おお！でた。
Boardから数字が渡ってきてマスの中に表示されているんですね。

###マス目をクリックされたときのイベントを設定する
jQueryだと

```javascript
$('button.square').click(() => {
  // 何か処理
});
```
素のJavaScriptだと

```javascript
document.querySelectorAll('button.square').forEach((node) => {
  node.addEventListener('click', () => {
	  // 何か処理
  });
});
```
となり、HTMLとは別個に記述します。

JSXの場合HTMLと一緒に書くようです。

```react
class Square extends React.Component {
  render() {
    return (
      <button
        className="square"
        onClick={() => {
          // 何か処理
        }}
      >
        {this.props.value}
      </button>
    );
  }
}
```
た、多分JSXのほうがｲｲﾖﾈ・・・。
今はこの独特の書き方が慣れてないんで微妙ですが、たくさん書けば良さを実感できるんでしょうか。

マス目の中身を**X**に変更する処理は、jQueryや素のJavaScriptのようにDOMAPIを呼ぶ書き方ではなく、stateを用いる方法で実現します。(コンパイル後には結局DOMAPIを呼ぶ書き方に変わりますが)

Square
・`constructor`を追加し、引数に`props`を設ける
・`super(props)`で継承元のコンストラクターを呼ぶ。お決まり
・`state`に`value`プロパティを持つオブジェクトを設定。`value`プロパティの値は`props.value`を設定
・`onClick`内では`setState`する
・レンダー時のマス目の値は`props.value`から`state.value`に変更する

```react:Square
class Square extends React.Component {
  constructor(props) {
    super(props);
    this.state = { value: this.props.value };
  }

  render() {
    return (
      <button
        className="square"
        onClick={() => {
          this.setState({ value: 'X' });
        }}
      >
        {this.state.value}
      </button>
    );
  }
}
```
結果
![ダウンロード (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/79cd3135-7162-aa9f-2200-e1bab5c75c96.gif)
ブラウザで確認時、Reactのコンポーネントツリーを調べる拡張機能があるらしい。
[React Devtools 拡張機能](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=ja)

###マス目の制御を行う
現在のマス目の内容の状態を取得するには、Board側から各Squareに取りに行くイメージでやればいいと思いがちですが、可読性が落ちたり、バグが起きやすいとのことです。
Board側で各Square側の状態を保持しておいて、propsを用いてやり取りするのがベストらしいです。

具体的にはまず、Board側にSquareに渡す値の情報を保持します。
→　マス目情報を長さ9つの配列として保持します。

####Boad側

```react:Board
class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = { squares: Array(9).fill(null) };
  }
```
[Array.prototype.fillの説明](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/fill)

renderSquareメソッドで配列のi番目を渡すようにします。

```react:Board
  renderSquare(i) {
    return <Square value={this.state.squares[i]} />;
  }
```
そしてSquareからBoardを更新してもらうために、Squareに対して**更新用の関数**を渡してあげます。
上の`value={this.state.squares[i]}`に続いて`onClick`を定義。

```react:Board
renderSquare(i) {
    return (
      <Square
        value={this.state.squares[i]} // クロージャでiが保持されている
        onClick={() => {
          const squares = Object.create(this.state.squares); // 別の配列としてクローン
          squares[i] = 'X';
          this.setState({ squares: squares });
        }}
      />
    );
  }
```
更新処理が書かれたこの関数ごとSquareに渡るイメージです。
Square側のpropsに入っているイメージ。

**iについて**
`renderSquare(i)`のところの`i`はクロージャによって、最初にレンダーされた時の各Squareごとの値を保持しています。
[クロージャ](https://developer.mozilla.org/ja/docs/Web/JavaScript/Closures)

**onClick内の更新処理について**
Reactのstateは直接更新は推奨されていないので、一度`Object.create`でコピーを作ってから、そのコピーを書き換えて、`setSate`で更新しています。
[なぜ推奨されないか](https://ja.reactjs.org/tutorial/tutorial.html#why-immutability-is-important)

チュートリアルではコピーのところは`this.state.squares.slice()`でしたが、これは配列の中身がプリミティブ値(数値、文字列、真偽値)の1次元配列の時のみ有効です。配列の中にオブジェクトや、さらに配列が入っていたら、完全なコピーができません。(ディープコピー)

コピー元の配列(state)と同じメモリの場所を参照してしまい、この後これに変更を行ってしまうと、「元のstateを変更しない」に反します。
このチュートリアルでは1次元の数値の配列なのでOKですが、今後それ以外のケースが出てきたときに誤ってsliceを使わないように`Object.create`で慣れておこうと思います。
[Array.prototype.slice()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/slice)


####Square側
・onClickの中で自身のstateを更新していたのをBoardから渡ってきたpropsのonClickを呼ぶように変更します。
・さらに、マス目に表示していた`this.state.value`は`this.props.value`に変更します。
　これを行うことにより、ボタン押下時のBoard側で変更されたstateの値がこのpropsに反映されます。
・stateを持つ必要がなくなったので、`constructor`を削除します。

```react:Square
class Square extends React.Component {
 
  // constructorは不要になったので削除
 
  render() {
    return (
      <button
        className="square"
        onClick={() => {
          this.props.onClick(); // BoardのonClickを呼び出す。
        }}
      >
        {this.props.value} // state → propsに変更
      </button>
    );
  }
}
```

ここまでのBoardとSquareの全文

```react:SquareとBoard
class Square extends React.Component {
  render() {
    return (
      <button
        className="square"
        onClick={() => {
          this.props.onClick();
        }}
      >
        {this.props.value}
      </button>
    );
  }
}

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

  renderSquare(i) {
    return (
      <Square
        value={this.state.squares[i]}
        onClick={() => {
          const squares = Object.create(this.state.squares);
          squares[i] = 'X';
          this.setState({ squares: squares });
        }}
      />
    );
  }

  render() {
    const status = 'Next palayer: X';

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

###関数コンポーネント
Squareはstateを持たなくてよくなったので、React.Componentを継承する必要もなくなります。
もっと簡潔に書けるようになるみたいです。

```react:Square
function Square(props) { // 継承がなくなった
  return (
    // onClickのところがシンプルに
    // あとpropsにthisがいらない。引数で来ているので
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  );
}
```

###手番の処理(XやOを実装)
先手は`X`にします。
Boardに「次の手番はだれか」を判定するためのフラグを設けます。
→「次の手番は`X`だ」というフラグを設けて、その値の`true` `false`で判定します。
その判定により、`X`と`O`を代入します。

Board

```react:Board
class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(null),
      xIsNext: true, // 先手はXなので初期値true
    };
  }

  renderSquare(i) {
    return (
      <Square
        value={this.state.squares[i]}
        onClick={() => {
          const squares = Object.create(this.state.squares);

          // xIsNextがtrueならX、それ以外なら○
          squares[i] = this.state.xIsNext ? 'X' : 'O';

          // xIsNextは現在の反対を代入することで手番が変わる
          this.setState({ squares: squares, xIsNext: !this.state.xIsNext });
        }}
      />
    );
  }
```
ついでに画面に表示していた文字のところの`Next palayer:`も動的に書き換わるようにします。

```react:Board
  render() {
    const status = 'Next palayer: ' + (this.state.xIsNext ? 'X' : 'O');
```
あれ？でもこれここの`status`を書き換えて意味あるの？って思っちゃいました。
なぜなら動的に変更するものは`state`に入れておかないとダメとおもっていたので・・・。
この`render`がマス目を毎回クリックするたびに毎回動くなら大丈夫ですけど・・・。

→　確認したら毎回動いてました・・・。
でも毎回動くのって無駄なような気が・・・。
毎回このhtmlを作成しているってことですよね。
このチュートリアルは要素数が少ないからいいですけど、普通のwebアプリを作ろうとしたらめちゃめちゃ要素数多いページとかあるのにレスポンス大丈夫なんでしょうか・・・。
後日調べたいと思います。


###ゲーム勝者の判定
動かしてみると分かりますが、今のままだとゲームが終わった後に何度でも入力が出来てしまいます。
なので、勝敗が着いたら何もさせないような制御を実装します。
また、どっちが勝ったかを分かり易くする為に、「勝者: ○○」の文字を表示させようと思います。

チュートリアルでは勝敗を判定してくれる関数を用意してくれています。これをコピーして貼り付けます。

チュートリアルではBoardの外に書いてますが、私は中に入れました。(renderの下)

```react:Board
// 勝敗判定関数
  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;
  }
```

勝者を表示させる`Next palayer:`の部分を変更します。

```react:Board
render() {
    const winner = this.calculateWinner(this.state.squares);

    let status;
    if (winner) {
      status = '勝者：' + winner;
    } else {
      status = '次の手番: ' + (this.state.xIsNext ? 'X' : 'O');
    }
```
最後に

- マス目が押された時に勝敗が決まっていたら
- 既にマス目が埋まっていたら

何も処理しないようにします。

```react:Board
renderSquare(i) {
    return (
      <Square
        value={this.state.squares[i]}
        onClick={() => {
          const squares = Object.create(this.state.squares);
          if (this.calculateWinner(squares) || squares[i]) {
            // 何もせずにreturn
            return;
          }

          squares[i] = this.state.xIsNext ? 'X' : 'O';
          this.setState({ squares: squares, xIsNext: !this.state.xIsNext });
        }}
      />
    );
  }
```
ifのところは
左の式は、this.calculateWinner(squares)の戻り値が`null`以外なら勝敗が決したということです。
右の式は、`suare[i]`が`null`以外なら既にマス目に値が入っているということです。


**以下、完成した全文**

```react:index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import * as serviceWorker from './serviceWorker';

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

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

  renderSquare(i) {
    return (
      <Square
        value={this.state.squares[i]}
        onClick={() => {
          const squares = Object.create(this.state.squares);
          if (this.calculateWinner(squares) || squares[i]) {
            return;
          }

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

  render() {
    const winner = this.calculateWinner(this.state.squares);

    let status;
    if (winner) {
      status = '勝者：' + winner;
    } else {
      status = '次の手番: ' + (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>
    );
  }

  // 勝敗判定関数
  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;
  }
}

class Game extends React.Component {
  render() {
    return (
      <div className="game">
        <div className="gmae-board">
          <Board />
        </div>
        <div className="game-info">
          <div>{/* status */}</div>
          <div>{/* TODO */}</div>
        </div>
      </div>
    );
  }
}

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

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
```
**動作結果**
![ダウンロード (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/f78fd977-5d57-e18c-b9c2-7452cbb2a08d.gif)

#おわりに
ここまでで一旦、三目並べ~~ゲーム~~が完成です。
Qiitaに書きながらなのでなかなか苦労しました・・・汗

チュートリアルではこの後「タイムトラベル機能」の追加の手順があるので、次回はそちらをやっていこうと思います。
履歴から手順を戻すやつですね。


GitHub・・・公式にソースあるしいらないかｗ

→　[次回](https://qiita.com/GodPhwng/items/1a4c2e0b784c7a4f8b54)