Reactのチュートリアルを全部やってみた

Reactのチュートリアルを全部やってみたので、コードを共有します。
いわゆる「〇×ゲーム」を作るというやつです。

# というのも
最初のほうはすべてコードの指示があるのですが、文書が進むにしたがってコードの記載が減っていきます。最後は宿題(?)のような5個の追加実装をやってみようみないな感じになります。そのあたりで躓いている方がもしいたら参考になるかと思いましたので共有します。もちろん、自分で考えて実装をしたほうがためになりますし、間違えがあるかもしれないので、参照する場合にはそのあたりをご了承ください。

あとは、自分の備忘目的というのもあります。

# チュートリアルをするにあたって
チュートリアルにも記載があるのですが、JavaScriptの基本的な知識とES2015を少し知っている必要があります。

# チュートリアルへのリンク
https://facebook.github.io/react/tutorial/tutorial.html

# 日本語訳など
Reactの公式ドキュメントが2016年10月に更新されたようで、現在公式の日本語訳がありません。日本語訳とそのあたりの情報は現在こちらで確認できます。
http://mae.chab.in/archives/2943

# コードへのリンク
Reactのチュートリアルで紹介されていた[CODEPEN](http://codepen.io/)のReactのチュートリアル用のコードをforkして作成しています。
https://codepen.io/tyahha/pen/RKbrwO

※Apple Penを思い出したのは僕だけではないはずです

## 動きだけ見たい場合はこちら
https://s.codepen.io/tyahha/debug/RKbrwO

# Wrapping Upの対応概要
訳は上述の[こちらのサイト](http://mae.chab.in/archives/2943)より引用させていただいております。

## 1. Display the move locations in the format "(1, 3)" instead of "6".
訳：動作の場所を「6」ではなく「(1, 3)」というフォーマットで表示させてみましょう。

- Gameコンポーネントのstateのhistoryにselectを追加して、そのときどのマスをクリックしたかを保持

```Gameのstate
    this.state = {
      history: [
        {
            squares: Array(9).fill(null),
            select: null,
        },
      ],
     ...
 ```
 
 - Game#handleClickに保存処理を追加
 
 ```Game#handleClick
 ...
     this.setState({
      history: history.concat([{squares, select: i}]),
...
 ```

- 選択値(0～8)を「(X, Y)」という文字列に変換する関数を用意。
 これはいらないかもしれません。なんとなくあったほうがわかりやすかと思って。

``` Game#selectToDisplay
  selectToDisplay(select) {
    return `(${Math.floor((select)/3) + 1}, ${select%3 + 1})`;
  }
```

- Game#renderの「desc」を変更
ついでに、そのターンで'X'と'O'のどっちを置いたかわかるようにしておきました。

```Game#render
  ...
    const moves = history.map((step, move) => {
      const desc = move ? `Move #${move} ${this.selectToDisplay(step.select)}:${move % 2 ? 'X' : 'O'}` : 'Game start';
  ...

```

## 2. Bold the currently-selected item in the move list.
訳：動作リストで現在選択されているアイテムを太字にしてみましょう。

- 上記で追加したselectを利用
- Game#render内でmovesを作成するmap内にて現在選択しているhistory(current)のselectと一致するhistoryがあった場合はリンクを<b></b>で囲むように修正。

```Game#render
  ...
    const moves = history.map((step, move) => {
      const desc = move ? `Move #${move} ${this.selectToDisplay(step.select)}:${move % 2 ? 'X' : 'O'}` : 'Game start';
      let anchor = <a
            href="#"
            onClick={() => this.jumpTo(move)}
          >{desc}</a>
      let link;
      if (current.select === step.select) {
        link = <b>{anchor}</b>;
      }
      else {
        link = anchor;
      }
      
      return (
        <li key={move}>
          {link}
        </li>
      );
    });
  ...
```

## 3. Rewrite Board to use two loops to make the squares instead of hardcoding them.
訳：マスをハードコーディングする代わりに2つのループを使って書き直してみましょう。

- コードを見てもらうしかない。
- 変数を用意しないでJSX内でループを記述するやり方があるのだろうか？

``` Board#render
  render() {
    const rows = [];
    for (let y = 0; y < 3; y++) {
      const squares = [];
      for (let x = 0; x < 3; x++) {
        squares[x] = this.renderSquare(y * 3 + x);
      }
      rows.push(
        <div key={y} className="board-row">
          {squares}
        </div>
      );
    }
    
    return (
      <div>{rows}</div>
    );
```

## 4. Add a toggle button that lets you sort the moves in either ascending or descending order.
訳：動作リストを昇順、降順で並び替えるトグルボタンを追加してみましょう。

- Gameのstateの現在のソート状態を保持するstate「isDesc」を追加
 -  なぜisAscにしなかったのかはわかりません。
- トグルボタンを追加するときに右側の表示が多くなってきたので「Next player: O」とかでるヘッダのようなものをBoardの上に移動。

``` Game#constructor
  constructor() {
    super();
    this.state = {
      history: [
        {
            squares: Array(9).fill(null),
            select: null,
        },
      ],
      xIsNext: true,
      stepNumber: 0,
      isDesc: false,     // ←　これ
    };
  }
```

- ソートの状態を変更する「Game#sortMoves」を追加

``` Game#sortMoves
  sortMoves(isDesc) {
    this.setState({isDesc});
  }
```

- Game#renderにて<ol>にreversedを付けるためにreturn前で<ol>の定義と慣れ控え

``` Game#render(return前)
    let ol;
    if (this.state.isDesc) {
      ol = <ol reversed>{moves.reverse()}</ol>
    }
    else {
      ol = <ol>{moves}</ol>
    }
```

- Game#renderのreturnするJSXにol埋め込みとソートボタン表示

```
          <div>
            <span>select sort: </span>
            <label><input type="radio" onChange={() => this.sortMoves(false)} checked={!this.state.isDesc} />asc</label>
            <label><input type="radio" onChange={() => this.sortMoves(true)} checked={this.state.isDesc} />desc</label>
          </div>
          {ol}
```

## 5. When someone wins, highlight the three squares that caused the win.
訳：どちらかが勝った時に、どの手で勝ったか3つのマスをハイライトさせてみましょう。

- チュートリアルで用意されている関数calculateWinnerを改造して勝ちの決め手になった列(line)も返却するようにした

```calculateWinner
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],
  ];
  const createWinner = (charactor, line) => {
    return {charactor, line}
  };
  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 createWinner(squares[a], lines[i]);
    }
  }
  return createWinner(null, []);
}
```

- createWinnerのI/Fを変えてしまったので使用箇所を変更

```Game#handleClick
  ...
    if (calculateWinner(squares).charactor || squares[i]) {
      return;
    }
  ...
```

```Game#render
  render() {
    const history = this.state.history,
          current = history[this.state.stepNumber],
          squares = current.squares,
          winner = calculateWinner(squares);

    let status;
    if (winner.charactor) {
      status = `Winner: ${winner.charactor}`;
    }
    else {
      status = `Next player: ${this.state.xIsNext ? 'X' : 'O'}`;
    }
    ...
```

- Boardコンポーネントに勝ちの決め手の列を渡す

``` Game#render
        ...
          <Board
            onClick={(i) => this.handleClick(i)}
            squares={squares}
            select={current.select}
            winLine={winner.line}
          />
        ...
```

- Boadコンポーネントはそれを受けてSquareコンポーネントにハイライトをするかどうかの指示を出す。
 - 調査中メモ：Propsに空の配列を渡すとプロパティがundefinedになる？

```Boad
class Board extends React.Component {
  renderSquare(i, highLight) {
    return <Square
             key={i}
             value={this.props.squares[i]}
             onClick={() => this.props.onClick(i)}
             highLight={highLight} // ←これ
           />;
  }
  render() {
    const rows = [];
    for (let y = 0; y < 3; y++) {
      const squares = [];
      for (let x = 0; x < 3; x++) {
        const index = y * 3 + x,
              highLight = this.props.winLine ? this.props.winLine.includes(index) : false ;
        squares[x] = this.renderSquare(index, highLight);
      }
      rows.push(
        <div key={y} className="board-row">
          {squares}
        </div>
      );
    }
    
    return (
      <div>{rows}</div>
    );
  }
}
```

- Squareコンポーネントはハイライトの指示を受けてクラスを付けたし

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

- ハイライト用のCSSクラスを追加
黄色にしました。

```CSS
.square.highlight {
  background: yellow;
}
```

## その他
日本人的感覚で「X」から開始ではなく「O」から開始にしました。
GameのstateのxIsNextの初期値をfalseにするだけです。

# 感想等
- JavaScriptにもScalaのようなif式、for式がほしい
- 条件が複雑になるとJSX記述で悩むことが多い
 -  変数に落とすか、コンポーネント化してしまうか。

# 最後に
- もう少しエレガントな解があったらコメントなど頂けると嬉しいです。