React のチュートリアルを Typescript でやってみた3（タイムトラベルの実装）【完】

## 目次
- [React のチュートリアルを Typescript でやってみた【環境構築編】](https://qiita.com/humi/items/49a7472e9a10558ea5c0)
- [React のチュートリアルを Typescript でやってみた【環境構築編2】](https://qiita.com/humi/items/72485614151fe564dceb)
- [React のチュートリアルを Typescript でやってみた【ソース準備編】](https://qiita.com/humi/items/72485614151fe564dceb)
- [React のチュートリアルを Typescript でやってみた1](https://qiita.com/humi/items/72485614151fe564dceb)
- [React のチュートリアルを Typescript でやってみた2](https://qiita.com/humi/items/f56214d859a0cf37a895)
- [React のチュートリアルを Typescript でやってみた3](https://qiita.com/humi/items/33e34183ccab3ff60a3b) ←ここ


## 概要
チュートリアル通りにやりますが、端折る箇所もあると思います。  
各々保管をお願いいたします。  

## 公式のチュートリアルページ
* [チュートリアル：React の導入](https://ja.reactjs.org/tutorial/tutorial.html)

## 今回の実施チュートリアル内容
### タイムトラベル機能の追加
1. 着手の履歴の保存
1. State のリフトアップ、再び
1. 過去の着手の表示
1. key を選ぶ
1. タイムトラベルの実装

## 1.着手の履歴の保存

特にソースの変更なし
どこにhistoryを作成するべきか考える。

## 2.State のリフトアップ、再び

game.tsxに作成する必要があるためStateとPropsを作成。
historyに関しては、1つの要素に1ステージのデータが入っているとしたものを作成しました。
こうしないと後々エラーになりました(´・ω・｀)

```tsx:game.tsx
type stage = { squares: Array<String>; }

type State = {
  history: Array<stage>;
  xIsNext: boolean;
}

type Props = {}
```

その後コンストラクタを追加

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

board.tsxの書き換え
game.tsxにStateを持つことになりこれまでデータを保持していたboard.tsxを大まかに変更する必要があります。

変更1：SteteからPropsに変更する。
Steteを削除し以下のPropsを作成

```tsx:board.tsx
type Porps = {
  squares: Array<String>;
  onClick: (i: number) => void;
}
```

変更2：classの引数とコンストラクタの変更

```tsx:board.tsx
class Board extends React.Component<Porps> {

  constructor(props: Porps) {
    super(props)
  }
```

変更3：renderSquareの内容の変更

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

変更4：handleClick及びcalculateWinnerのfunctionをgame.tsxに移動

大まかにはこのくらいまだまだ変更する必要がありますがとりあえずはって感じです。

game.tsxのrenderを書き換えていきます。
以下のような感じになりました。

```tsx:game.tsx
  render() {
    const history = this.state.history;
    const current = history[history.length - 1];
    const winner = this.calculateWinner(current.squares);
    let status;
    if (winner) {
      status = 'Winner: ' + winner;
    } else {
      status = 'Next player: ' + (this.state.xIsNext ? 'X' : '○');
    }
    
    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>{/* TODO */}</ol>
        </div>
      </div>
    );
  }
}
```

game.tsxを書き換えた事によってboard.tsxを書き換えます。
renderの部分のstatusについて削除しました。

```tsx:board.tsx
  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>
    );
  }
```

game.tsxに移動したhandleClickを書き換えます。
以下のような感じになりました。
ここの`history: history.concat({ squares: squares }),`でかなり躓きました・・・
このようにするためにgame.tsxのStateがあのような形になった感じです。
もっといい方法があればオシエテクダサイ・・・

```tsx:game.tsx
  handleClick(i: number) {
    const history = this.state.history;
    const current = history[history.length - 1];
    const squares = current.squares.slice();
    if (this.calculateWinner(squares) || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext ? 'X' : '○';
    this.setState({
      history: history.concat({ squares: squares }),
      xIsNext: !this.state.xIsNext,
    });
  }
```

以上でゲームができるような形になったと思います。

## 3.過去の着手の表示

game.tsxのrenderに以下のものを追加します。

```tsx:game.tsx
  render() {
    const history = this.state.history.slice(0, this.state.stepNumber + 1);
    const current = history[this.state.stepNumber];
    const winner = this.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;
︙
        <div className="game-info">
          <div>{status}</div>
+          <ol>{moves}</ol>
        </div>
︙
```

この時点ではWARNINGが表示されますが以降のものを実装することによってなくなるので放置します。

## 4.key を選ぶ

特にソースの変更なし
概念的には理解する必要あり

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

movesの中身を変更します。
以下の変更でWARNINGが消えると思います。

```tsx:game.tsx
      return (
-        <li>
+        <li key={move}>
          <button /* onClick={() => this.jumpTo(move)} */>{desc}</button>
        </li>
      );
```

historyの機能のために
stepNumberを追加します。

```tsx:game.tsx
type State = {
  history: Array<stage>;
  xIsNext: boolean;
+  stepNumber: number;
}
︙
  constructor(props: Props) {
    super(props);
    this.state = {
      history: [{
        squares: Array(9).fill(null),
      }],
      xIsNext: true,
+      stepNumber: 0,
    };
  }
```

jumpToの実装

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

+          <button onClick={() => this.jumpTo(move)} >{desc}</button>
```

stepNumberの機能をhandleClickに反映させます。
チュートリアルのソースのままです。

```tsx:game.tsx
  handleClick(i: number) {
+    const history = this.state.history.slice(0, this.state.stepNumber + 1);
    const current = history[history.length - 1];
    const squares = current.squares.slice();
    if (this.calculateWinner(squares) || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext ? 'X' : '○';
    this.setState({
      history: history.concat({ squares: squares }),
+      stepNumber: history.length,
      xIsNext: !this.state.xIsNext,
    });
  }
```

最後にrenderする際の対象となるゲームのステージを設定します。

```tsx:game.tsx
    const history = this.state.history;
+    const current = history[this.state.stepNumber];
    const winner = this.calculateWinner(current.squares);
```

## まとめ
駆け足になりましたが、以上になります。
どのくらい吸収できたのかわかりませんがとりあえずチュートリアルは以上です。
何かしら自分で作ったほうがいいかもしれませんね。

ソースは以下
- [チュートリアル完成](https://github.com/humi3/react-tutorial)