reactチュートリアルをやってみる

Reactの理解がまだまだ浅いと感じ本家のチュートリアルをやってみる事にする。

reactはjsのライブラリになるのでjsが不安な時は一度jsを復習するべきである
英語だがこちらがES6を含めて復習できるのでオススメ
→https://developer.mozilla.org/en-US/docs/Web/JavaScript/A_re-introduction_to_JavaScript


## 開発環境の準備

コマンドラインで

```js
npx create-react-app sanmoku
```
を実行する。

それから元々あるsrcフォルダーの中身をすべて削除する。

それから```index.css```と``index.js``ファイルを作成する。

そのindex.jsにreact・reactDOM・cssを読み込む

```js
//index.js
import Reeat from 'react';
import ReactDOM from 'react-dom';
import './index'
```

この後公式では``npm start``で三目盤が表示されると書いてあるが、今回はまだ記述をしてないのでサーバーを立ち上げても何も表示されない。

あくまでも今回はreactを色々触ってみるのが目的なのでcssとjsは公式の物をそのまま使う。

後々はすべて自分で書けるようになりたいと思う。

##データをpropsで渡す

Boardという``renderSquera``メソッド内でpropsとしてvalueという名の値をBoardからSquareに渡す。

```js
renderSquare(i) {
      return <Square value={i} />;
  }
```

それを受け取るためにSquareのrenderメソッド内で渡された値を表示するため

```js
render() {
    return (
      <button className="square">
        {this.props.value}
      </button>
    );
  }
```
に書き換える。

これでBoardコンポーネントから子コンポーネントであるSquareコンポーネントにpropsを渡すことに成功した。

##インタラクティブなコンポーネントを作る
Squareコンポーネントがクリックされた時に✖になるようする。

そのためにはSquareコンポーネントにクリックされた場所を✖マークで埋めるにはクリックされた場所を記憶させる必要がある。

そこで使うのが``state``である。
stateを設定することで状態を管理することが出来る。

コンストラクタ(関数)内で``this.state``を設定すると状態を管理することが出来る。

javascriptのクラスはサブクラスのコンストラクタを定義する時に毎回``super``を呼ぶ必要がある。

→https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Classes


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

次にSquareのrenderメソッドを書き換えて、クリックされた時に今のstate=nullから変更する。

```js
render() {
    return (
        <button
            className="square"
            onClick={() =>
                this.setState({ value: 'X' })}
        >
        {this.state.value}
      </button>
    );
  }
```
onClickハンドラ内でthis.setStateを呼び出すことでbuttonがクリックされたら常に再レンダーをするように伝えられる。

そのthis.setStateの中の``value``が✖にしたので、どこのマスをクリックしても✖になる。

##Stateのリフトアップ
今はそれぞれのマスがそれぞれの状態を持っている。
しかしそれではゲーム判定が出来ないので9個まとめて管理する必要がある。

今回は親であるBoardコンポーネントで管理する。

```js
class Board extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            squares: Array(9).fill(null),
        };
    }
～～～
}
```
Boardにコンストラクタを追加して初期のstateとして9個のマス目に対応する9個のnull値をセットする。

また``renderSquare``メソッドはこのようになっている。
しかし今はSquare自体に``✖``を設定していたためBoardからのvalueを受け取る事はなかった。

これをBoardから受け取るようにセットする。

```js
renderSquare(i) {
        return <Square value={this.state.squares[i]}
     />;
  }
```
とすることでBoardで設定したvalueをSqareに渡すことが出来る。


###次にマスがクリックされた時の挙動を変更する。
今、どのマスに何が入ってるかを管理しているのはBoardである。
そのstateをSquareが更新できるようにする必要がある。

しかしBoardで設定されているstateはローカルの物なので異なるコンポーネントであるSquareからでは書き換える事が出来ない。

代わりにBoardからSquareに関数を渡すことでマスがクリックされた時にSquareにBoardの関数を呼ぶことで擬似的にSquareからBoardのstateの変更を可能にする。

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

これでBoardからSquareにはpropsとして``value``と``onClick``2つの値を渡している。
``onClick``はマスがクリックされた時にSquareが呼び出すものである。
Squareを以下のように書き換える。

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

こうすることでSquareがクリックされた時にBoardから渡されたonClickをコールして変更をすることが出来る。

流れとしては
1．``button``がクリックされると設定されている``onClick``が起動する。

2．``button``の``onClick``に設定されているBoardでセットしたonClickを呼び出す

3．renderSquareのonClickでセットした``handleClick``を呼び出す。

4.handleClickは定義してないのでエラー表示が起きる

handleClickをBoardで定義する。

```js
handleClick(i) {
        const squares =
            this.state.squares.slice();
        squares[i] = 'x';
        this.setState({ squares: squares });
    }
```

これによってすべてのマスをBoardで管理することで勝敗判定を出来るようになる。

Squareコンポーネントは自分でsateを管理せずBoardが管理するのでreactの単語でSquareコンポーネントは制御されたコンポーネントと呼ばれる。

そしてhandleClick内ではsquaresを直接変更する代わりに``slice()``を読んで配列のコピーを作成している。

##なぜ直接変更ではなく変更したコピーを作成するのか

一般的に変化するデータへのアプローチは2通りある。

・変更するデータを直接書き換える
・変更を加えた新しいデータを古いデータと置き換える

最終的な結果かは同じだが直接変更しないメリットがいくつかある。

・複雑な機能が簡単に実装できる
・変更の検出
・再レンダーのタイミング決定

##関数コンポーネント
stateを持たないシンプルなコンポーネントである。

Squareを関数コンポーネントで書き換える。

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

##ターンの処理
今まで〇が出てきてなかったので〇が出るように修正する。
デフォルトで先手を✖にする。
Boardのコンストラクタでstateの初期値を変更すればデフォルト値は変更できる。

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


プレイヤーが着手するたびに``xIsNext``の真偽値を反転させてゲーム状態を保存する。
BoardのhandleClick関数を書き換えて``xIsNext``の値を反転させる。

三項演算子を使って書く。
三項演算子について→https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Conditional_Operator

```js 
handleClick(i) {
        const squares =
            this.state.squares.slice();
        squares[i] = this.state.xIsNext? 'x':'o';
        this.setState({
            squares: squares,
            xIsNext: !this.state.xIsNext,
        });
    }
```
1つずつ理解していく
```js
squares[i] = this.state.xIsNext? 'x':'o';
```
1ターン目を例にする。
さっきコンストラクタで``xIsNext:true``と書いたので、この三項演算子では``ｘ``を取りクリックした場所に✖を置くようにする。(これだけでは✖は画面に表示されない)

それから下のコードで次のターンに移行する

```js
this.setState({
            squares: squares,//1
            xIsNext: !this.state.xIsNext,//2
        });
```
1では新しい✖を加えたデータを古いデータと置き換えるコードである。

2は次に〇のターンにするため``!``を使う事で``this.state.xIsNext``の反対(今回はtrue→false)を``xIsNext``に入れて〇のターンにする。

これによって交互に置くことが可能になった。

##勝者判定
決着判定のために以下の関数をファイルの最後に追加する。

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

>ここについては後ほど
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) 

Boardのrender内で``calculateWinner(squares)``を呼び出して勝敗がついてるかを確認する。
同時に``status``も書き換える。

```js
render() {
        const winner = calculateWinner(this.state.squares);
        let status;
        if (winner) {
            status='winner:'+winner
        } else {
            status ='Next player: '+ (this.state.xIsNext?'×':'〇');
        }
~~~
}
```

後は
・ゲームの決着がついてる時
・すでに置かれてるマスをクリックした時
には何も変化せず済むように記述する。

```js
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,
        });
```
``||``は論理演算子orである。

##最低限完成

これでオセロゲームとしては最低限遊べるようになった。

##タイムトラベルの実装

もしsquaresの配列を直接書き換え(ミューテート)していたらタイムトラベルの実装はとても難しかった。

しかし今回は配列のコピーを作り、配列をイミュータブルなものとして扱った。だからsquareの過去のデータを遡ることが出来る。

過去のsqusresの配列を``history``という名前の別の配列に保存する。
(二次元配列になる)

###リフトアップ
今まではマス自体を管理していたのでBoardで管理していたが、これからはボード全体を管理する必要があるので更に一段階リフトアップする必要がある。

そのため``Game``コンポーネントが``history``にアクセスできる必要があるのでトップレベルのGameコンポーネントでhistoryというstateを置く。

``history``stateをGameコンポーネント内に置くことでBoardコンポーネントから``square``stateを取り除ける。
これでBoardコンポーネントのデータを制御できるようになり過去の盤面をレンダー出来るようになる。

Gmaeコンポーネントに``history``sstateとターンを決める``xIsNext``stateを置く

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

次にBoardコンポーネントが`squares``と``onClick``プロパティをGameコンポーネントから受け取るようにする。
Board内には9個のマスにそれぞれ対応するクリックハンドラがあるのでSquareの位置を``onClick``ハンドラに渡してどのマスがクリックされたかを伝えるようにする。

これらをするには以下の作業が必要である。

・Boardの``constructor``を削除する
・Board内の``renderSquares``の中身の書き換え


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

これでGameコンポーネントのrender関数を更新することで画面を表示できるようになった。
だからBoard内のrender関数は``return``を除いて削除できる。

最後に``handleClick``メソッドをBoardコンポーネントからGmaeコンポーネントに一部修正して移動する。

このhandleClickにも``history``を追加する。

##過去の着手の表示
これで三目並べの履歴を記録してるので画面に表示させることが出来る。
mapメソッドを使って配列をマップして画面上にボタンを表現するreactを作る。

```js
        const winner = calculateWinner(current.squares)
        const move = history.map((step, move) => {
            const desc = move ? 'Go to move#' + move : 'Go to game start'
            return (
                <li>
                    <button onClick={() => this.junpTo(move)}>{desc}</button>
                </li>
            )
        })
        let status;
        if (winner) {
            status='winner:'+winner
        } else {
            status ='Next player: '+ (this.state.xIsNext?'×':'〇');
        }
    return (
      <div className="game">
        <div className="game-board">
                <Board
                    squares={current.squares}
                    oncClick={(i) => this.handleClick(i)}
                    />
        </div>
        <div className="game-info">
          <div>{status}</div>
          <ol>{move}</ol>
        </div>
      </div>
    );
  }
```
ゲームのターン毎に対応した``button``を持つ``li``を作る。
ボタンには``onClick``ハンドラを持ち``this.jumpTo()``というメソッドを呼び出す。

>同時にリストにkeyを設定するように言われた。
最悪なくても動くw


後``jumpTo``がまだ実装されていないのでそちらも実装していく。
その前にGameコンポーネントのstateに``stepNumber``を加える。
これは今何手目かを判断するのに使う。

##タイムトラベルの実装
まずkeyを設定する。

```js
 const desc = move ? 'Go to move#' + move : 'Go to game start'
            return (
                <li key={move}>
                    <button onClick={() => this.junpTo(move)}>{desc}</button>
                </li>
            )
```

後``stepNumbeer``の初期値を設定する

```js
constructor(props) {
        super(props);
        this.state = {
            history: [{
                squares:Array(9).fill(null)
            }],
            xIsNext: true,
            stepNumber: 0,
        }
    }
```
次に``jumpTo``メソッドを定義する。
stepNumberを更新されるようにして、それが偶数だったらxIsNextがtrueになるよう設定する。

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

次にマス目をクリックした時に実行されるhandleClickメソッドをいくつか変更したら完了である。
・stepNunberの更新
・新しい手を打った時に更新できるようにする。

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