React公式チュートリアル『改良のアイディア』を実装してみた

## 前書き
SPAやってみたいなあと思っていたので、以下の記事を読んでReactを初めてみました。
[ReactとVueのどちらを選ぶか - Qiita](https://qiita.com/yoichiwo7/items/236b6535695ea67b4fbe)
[https://qiita.com/teradonburi/items/fb91e5feacab5071cfef - Qiita](https://qiita.com/teradonburi/items/fb91e5feacab5071cfef)
[◆ Spring Bootで作成したAPIをReactからGETする - Qiita](https://qiita.com/Haru_3/items/d1644226682dfbc3a9a3)

最初にやってみるのはやはり公式ということで、[チュートリアル：React の導入](https://ja.reactjs.org/tutorial/tutorial.html)にチャレンジ。
基本的な`props`や`state`の考え方が分かりやすく、リファクタリング手順も載っているのでかなり見返すことになりそうです。

チュートリアルの最後に以下のような記載がありましたので、こちらにもチャレンジ。
> 時間がある場合や、今回身につけた新しいスキルを練習してみたい場合に、あなたが挑戦できる改良のアイデアを以下にリストアップしています。

見返した自分の役に立つようチャレンジ時に考えていたことも書いているので、
参考になれば幸いです。

## 実装
コードは[最終手順](https://CodePen.io/gaearon/pen/gWWZgR?editors=0010)からのステップアップ形式でやっていきます。

### 1. 履歴内のそれぞれの着手の位置を (col, row) というフォーマットで表示する。
イメージはこんな感じ。
![react-tutorial-improve-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205862/c28f6bce-a819-1ce2-f0cc-56425826eb0f.png)

まず、変更したいのは以下の部分です。

```jsx:jsx
// 省略
class Game extends React.Component {
  constructor(props) {
    // 省略
  }

  handleClick(i) {
    // 省略
  }

  jumpTo(step) {
    // 省略
  }

  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 (
        // 省略
      );
    });
    // 省略
  }
}
// 省略
```

この部分で以下のように表示できれば。。。

```jsx:jsx
      const desc = move ?
        'Go to move #' + move + '(' + col + ', ' + row + ')' :
        'Go to game start';
```

では、`col`と`row`はどのように求めるか。
1マスを表す`Square`コンポーネントで、自身の座標を持つのが良さそうかな？と最初は考えました。
しかし、[State のリフトアップ](https://ja.reactjs.org/tutorial/tutorial.html#lifting-state-up)で行ったように、
> Board が各 Square に、現時点の state がどうなっているか問い合わせればよいだけでは、と思うかもしれません。
> React でそれをすることも可能ですが、コードが分かりにくく、より壊れやすく、リファクタリングしづらいものになるのでお勧めしません。

とあります。
実際に`Square`コンポーネント(とその親の`Board`コンポーネント)はステートレスの状態になっているので、
`Game`コンポーネントで管理することになりそうです。

ではまず、`Square`コンポーネントを生成している部分を見ていきます。
このプログラム内でクリックした場所を表すのは、以下の部分です。

```jsx:jsx
// 省略
class Board extends React.Component {
  renderSquare(i) {  // ← 2. ここの引数として渡され
    return (
      <Square
        value={this.props.squares[i]}
        onClick={() => this.props.onClick(i)}  // ← 3. ここでGameコンポーネントから受け取った関数に渡される
      />
    );
  }

  render() {
    return (
      <div>
        <div className="board-row">
          {this.renderSquare(0)}  // ← 1. ここでべた書きされているセル番号が
          {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`コンポーネントから渡す関数オブジェクトに渡されるわけですね。
この関数オブジェクトは以下のように定義されています。

```jsx:jsx
// 省略
class Game extends React.Component {
  // 省略
  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
    });
  }

  // 省略

  render() {
    // 省略
    return (
      <div className="game">
        <div className="game-board">
          <Board
            squares={current.squares}
            onClick={i => this.handleClick(i)} // ← ここでpropsとして渡されている
          />
        </div>
        <div className="game-info">
          <div>{status}</div>
          <ol>{moves}</ol>
        </div>
      </div>
    );
  }
}
```

ですので、`handleClick`メソッド内部で`state`として保持してあげれば良さそうです。
現状`state`として保持されている情報は以下の3つです。

- history: `squares`をプロパティとして持つ**Object**の配列
- stepNumber: いま何手目の状態を見ているのかを表す数字
- xIsNext: 次が`X`の手番であるかどうかを表す真偽値

保持したい情報は`各手番で押されたセルの座標`ですので、`history`と1対1の関係ですね。
ここで、`history`が「**Object**の配列」であるということが大切になります。
実際、私がチュートリアルをやっていたときは「`history`ってオブジェクトでラップせずに配列の配列でよくね」と考えていましたが、
ここまで手を進めて拡張性のためにわざわざしていたのだと気付きました。（違ったらすみません。。。）
ということで、`history`の配列内オブジェクトにプロパティを追加します。

```diff:jsx
// 省略
class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      history: [
        {
-         squares: Array(9).fill(null)
+         squares: Array(9).fill(null),
+         location: {
+           col: null,
+           row: null,
+         },
        }
      ],
      stepNumber: 0,
      xIsNext: true
    };
  }
  // 省略
}
```

初期値は`null`ですが、`handleClick`メソッド内で値を設定します。

`col`は`0~8`のセル番号を`3で割った余り`にすればよいでしょう。
コードで表すと、`i % 3`となります。

`row`は`0~8`のセル番号を`3で割った商`にすればよいでしょう。
コードで表すと、`Math.trunc(i / 3)`となります。
※`Math.floor`でも良いという記事もあります。
今回は`i`が0を含む自然数ですので問題にはなりませんが、`Math.trunc`の方が定義としては正しいでしょう。
`Math.trunc(-6.5 / 3) -> -2`と`Math.floor(-6.5 / 3) -> -3`という違いがあります。

```diff:jsx
// 省略
class Game extends React.Component {
  // 省略
  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
+         squares: squares,
+         location: {
+           col: i % 3,
+           row: Math.trunc(i / 3),
+         },
        }
      ]),
      stepNumber: history.length,
      xIsNext: !this.state.xIsNext
    });
  }
  // 省略
}
```

座標を保持できたので、ボタンに表示します。

```diff:jsx
// 省略
class Game extends React.Component {
  // 省略
  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 move #' + move + '(' + step.location.col + ', ' + step.location.row + ')' :
        'Go to game start';
      return (
        <li key={move}>
          <button onClick={() => this.jumpTo(move)}>{desc}</button>
        </li>
      );
    });
    // 省略
  }
}
// 省略
```

これで完成です。

<details><summary>【クリックで展開】ここまでのコードは以下になります。（[CodePenはこちら](https://codepen.io/ExoticToybox/pen/jOPKwbr)）</summary><div>

```html:html(変更なし)
<div id="errors" style="
  background: #c00;
  color: #fff;
  display: none;
  margin: -20px -20px 20px;
  padding: 20px;
  white-space: pre-wrap;
"></div>
<div id="root"></div>
<script>
window.addEventListener('mousedown', function(e) {
  document.body.classList.add('mouse-navigation');
  document.body.classList.remove('kbd-navigation');
});
window.addEventListener('keydown', function(e) {
  if (e.keyCode === 9) {
    document.body.classList.add('kbd-navigation');
    document.body.classList.remove('mouse-navigation');
  }
});
window.addEventListener('click', function(e) {
  if (e.target.tagName === 'A' && e.target.getAttribute('href') === '#') {
    e.preventDefault();
  }
});
window.onerror = function(message, source, line, col, error) {
  var text = error ? error.stack || error : message + ' (at ' + source + ':' + line + ':' + col + ')';
  errors.textContent += text + '\n';
  errors.style.display = '';
};
console.error = (function(old) {
  return function error() {
    errors.textContent += Array.prototype.slice.call(arguments).join(' ') + '\n';
    errors.style.display = '';
    old.apply(this, arguments);
  }
})(console.error);
</script>
```
```css: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;
}
```
```jsx:jsx
function Square(props) {
  return (
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  );
}

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

class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      history: [
        {
          squares: Array(9).fill(null),
          location: {
            col: null,
            row: 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,
          location: {
            col: i % 3,
            row: Math.trunc(i / 3),
          },
        }
      ]),
      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 + '(' + step.location.col + ', ' + step.location.row + ')' :
        '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"));

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;
}
```
</div></details>

### 2. 着手履歴のリスト中で現在選択されているアイテムをボールドにする。
ボタンのテキストを**`font-weight: bold`**にします。
イメージはこんな感じ。
![react-tutorial-improve-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205862/ac925d34-421a-8e54-6cf5-fb6a0d717865.png)

なんか1.より2.の方が簡単な気もしますが。。。
まずCSSに**`bold`**のスタイルを作成します。

```css:css
/* 省略 */
.text-bold {
  font-weight: bold;
}
```

このスタイルをどのように当てるかですが、チュートリアル内で紹介されている[一段階ずつ学べるガイド](https://ja.reactjs.org/docs/hello-world.html)の項目の中に[2. JSX の導入](https://ja.reactjs.org/docs/introducing-jsx.html)というものがあります。
これによると、
> あらゆる有効な JavaScript の式を JSX 内で中括弧に囲んで使用できます。

ということなので、`className`の右辺で中括弧を使用した分岐ができれば良さそう。

```diff:jsx
// 省略
class Game extends React.Component {
  // 省略
  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 + '(' + step.location.col + ', ' + step.location.row + ')' :
        'Go to game start';
      return (
        <li key={move}>
-         <button onClick={() => this.jumpTo(move)}>{desc}</button>
+         <button
+           onClick={() => this.jumpTo(move)}
+           className={move === this.state.stepNumber ? 'text-bold' : ''}
+         >
+           {desc}
+         </button>
        </li>
      );
    });
    // 省略
  }
}
// 省略
```
※読みやすくするため、`onClick`と`className`の両プロパティをそれぞれ独立した行に配置

<details><summary>【クリックで展開】ここまでのコードは以下になります。（[CodePenはこちら](https://codepen.io/ExoticToybox/pen/GRJGEjb)）</summary><div>

```html:html(変更なし)
<div id="errors" style="
  background: #c00;
  color: #fff;
  display: none;
  margin: -20px -20px 20px;
  padding: 20px;
  white-space: pre-wrap;
"></div>
<div id="root"></div>
<script>
window.addEventListener('mousedown', function(e) {
  document.body.classList.add('mouse-navigation');
  document.body.classList.remove('kbd-navigation');
});
window.addEventListener('keydown', function(e) {
  if (e.keyCode === 9) {
    document.body.classList.add('kbd-navigation');
    document.body.classList.remove('mouse-navigation');
  }
});
window.addEventListener('click', function(e) {
  if (e.target.tagName === 'A' && e.target.getAttribute('href') === '#') {
    e.preventDefault();
  }
});
window.onerror = function(message, source, line, col, error) {
  var text = error ? error.stack || error : message + ' (at ' + source + ':' + line + ':' + col + ')';
  errors.textContent += text + '\n';
  errors.style.display = '';
};
console.error = (function(old) {
  return function error() {
    errors.textContent += Array.prototype.slice.call(arguments).join(' ') + '\n';
    errors.style.display = '';
    old.apply(this, arguments);
  }
})(console.error);
</script>
```
```css: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;
}

.text-bold {
  font-weight: bold;
}
```
```jsx:jsx
function Square(props) {
  return (
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  );
}

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

class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      history: [
        {
          squares: Array(9).fill(null),
          location: {
            col: null,
            row: 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,
          location: {
            col: i % 3,
            row: Math.trunc(i / 3),
          },
        }
      ]),
      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 + '(' + step.location.col + ', ' + step.location.row + ')' :
        'Go to game start';
      return (
        <li key={move}>
          <button
            onClick={() => this.jumpTo(move)}
            className={move === this.state.stepNumber ? 'text-bold' : ''}
          >
            {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"));

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;
}
```
</div></details>

### 3. Board でマス目を並べる部分を、ハードコーディングではなく 2 つのループを使用するように書き換える。
見た目に変更はありません。
こちらもとっかかりやすいですね。
対象は以下の部分です。

```jsx:jsx
// 省略
class Board extends React.Component {
  // 省略
  render() {
    const cols = [0, 1, 2];
    return (
      <div>
        <div className="board-row"> //                   ┐
          {this.renderSquare(0)}    // ┐                 │
          {this.renderSquare(1)}    // │ここがループ(1)   │
          {this.renderSquare(2)}    // ┘                 │
        </div>                      //                   │
        <div className="board-row"> //                   │
          {this.renderSquare(3)}    //                   │
          {this.renderSquare(4)}    //                   │ ここがループ(2)
          {this.renderSquare(5)}    //                   │
        </div>                      //                   │
        <div className="board-row"> //                   │
          {this.renderSquare(6)}    //                   │
          {this.renderSquare(7)}    //                   │
          {this.renderSquare(8)}    //                   │
        </div>                      //                   ┘
      </div>
    );
  }
}
// 省略
```

まずループ(1)からやっていきます。
JSXでは中括弧で囲まれた部分でJavascriptを使用できるので、以下のようにループします。

```diff:jsx
// 省略
class Board extends React.Component {
  // 省略
  render() {
+   const cols = [0, 1, 2];
    return (
      <div>
        <div className="board-row">
-         {this.renderSquare(0)}
-         {this.renderSquare(1)}
-         {this.renderSquare(2)}
+         {cols.map(col => this.renderSquare(col))}
        </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>
    );
  }
}
// 省略
```

このままでは[チュートリアルの「過去の着手の表示」項目](https://ja.reactjs.org/tutorial/tutorial.html#showing-the-past-moves)でも起きたように、
以下の警告も出力されているはずです。
> Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of "Board".

そのため、`renderSquare`メソッド内で呼び出している`Square`コンポーネントに`key`プロパティを追加します。

```diff:jsx
// 省略
class Board extends React.Component {
  renderSquare(i) {
    return (
      <Square
        value={this.props.squares[i]}
        onClick={() => this.props.onClick(i)}
+       key={i}
      />
    );
  }
  // 省略
}
// 省略
```

これでエラーが表示されなくなったので、他の`<div className="board-row">`にもループを適用します。

```diff:jsx
// 省略
class Board extends React.Component {
  // 省略
  render() {
    const cols = [0, 1, 2];
    return (
      <div>
        <div className="board-row">
          {cols.map(col => this.renderSquare(col))}
        </div>
        <div className="board-row">
-         {this.renderSquare(3)}
-         {this.renderSquare(4)}
-         {this.renderSquare(5)}
+         {cols.map(col => this.renderSquare(col + 3))}
        </div>
        <div className="board-row">
-         {this.renderSquare(6)}
-         {this.renderSquare(7)}
-         {this.renderSquare(8)}
+         {cols.map(col => this.renderSquare(col + 6))}
        </div>
      </div>
    );
  }
}
// 省略
```

そして、ループ(2)を実装します。

```diff:jsx
// 省略
class Board extends React.Component {
  // 省略
  render() {
+   const rows = [0, 1, 2];
    const cols = [0, 1, 2];
    return (
      <div>
-       <div className="board-row">
-         {cols.map(col => this.renderSquare(col))}
-       </div>
-       <div className="board-row">
-        {cols.map(col => this.renderSquare(col + 3))}
-       </div>
-       <div className="board-row">
-         {cols.map(col => this.renderSquare(col + 6))}
-       </div>
+       {rows.map(row => {
+         return (
+           <div className="board-row">
+             {cols.map(col => this.renderSquare(row * 3 + col))}
+           </div>
+         );
+       })}
      </div>
    );
  }
}
// 省略
```

2重ループは実装できましたが、また同じエラーが出ているので、`row`でループしている`<div className="board-row">`にも`key`プロパティを追加します。

```diff:jsx
// 省略
class Board extends React.Component {
  // 省略
  render() {
    const rows = [0, 1, 2];
    const cols = [0, 1, 2];
    return (
      <div>
        {rows.map(row => {
          return (
-           <div className="board-row">
+           <div
+             className="board-row"
+             key={row}
+           >
              {cols.map(col => this.renderSquare(row * 3 + col))}
            </div>
          );
        })}
      </div>
    );
  }
}
// 省略
```
※読みやすくするため、`className`と`key`の両プロパティをそれぞれ独立した行に配置

<details><summary>【クリックで展開】ここまでのコードは以下になります。（[CodePenはこちら](https://codepen.io/ExoticToybox/pen/XWbYgRZ)）</summary><div>

```html:html(変更なし)
<div id="errors" style="
  background: #c00;
  color: #fff;
  display: none;
  margin: -20px -20px 20px;
  padding: 20px;
  white-space: pre-wrap;
"></div>
<div id="root"></div>
<script>
window.addEventListener('mousedown', function(e) {
  document.body.classList.add('mouse-navigation');
  document.body.classList.remove('kbd-navigation');
});
window.addEventListener('keydown', function(e) {
  if (e.keyCode === 9) {
    document.body.classList.add('kbd-navigation');
    document.body.classList.remove('mouse-navigation');
  }
});
window.addEventListener('click', function(e) {
  if (e.target.tagName === 'A' && e.target.getAttribute('href') === '#') {
    e.preventDefault();
  }
});
window.onerror = function(message, source, line, col, error) {
  var text = error ? error.stack || error : message + ' (at ' + source + ':' + line + ':' + col + ')';
  errors.textContent += text + '\n';
  errors.style.display = '';
};
console.error = (function(old) {
  return function error() {
    errors.textContent += Array.prototype.slice.call(arguments).join(' ') + '\n';
    errors.style.display = '';
    old.apply(this, arguments);
  }
})(console.error);
</script>
```
```css: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;
}

.text-bold {
  font-weight: bold;
}
```
```jsx:jsx
function Square(props) {
  return (
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  );
}

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

  render() {
    const rows = [0, 1, 2];
    const cols = [0, 1, 2];
    return (
      <div>
        {rows.map(row => {
          return (
            <div
              className="board-row"
              key={row}
            >
              {cols.map(col => this.renderSquare(row * 3 + col))}
            </div>
          );
        })}
      </div>
    );
  }
}

class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      history: [
        {
          squares: Array(9).fill(null),
          location: {
            col: null,
            row: 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,
          location: {
            col: i % 3,
            row: Math.trunc(i / 3),
          },
        }
      ]),
      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 + '(' + step.location.col + ', ' + step.location.row + ')' :
        'Go to game start';
      return (
        <li key={move}>
          <button
            onClick={() => this.jumpTo(move)}
            className={move === this.state.stepNumber ? 'text-bold' : ''}
          >
            {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"));

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;
}
```
</div></details>

### 4. 着手履歴のリストを昇順・降順いずれでも並べかえられるよう、トグルボタンを追加する。
イメージはこんな感じ。
![react-tutorial-improve-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205862/3db7c2fd-6af9-9578-8533-eecfebe8796c.png)

こちらは一見簡単そうに見えます。
具体的には、「Reverse history order」のボタンを用意し、クリックした際に`history`プロパティを逆順にする`reverseHistoryOrder`メソッドを追加します。

```diff:jsx
// 省略
class Game extends React.Component {
  // 省略
+ reverseHistoryOrder() {
+   this.setState({
+     history: this.state.history.slice().reverse(),
+   });
+ }

  render() {
    // 省略
    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>
+         <button onClick={() => this.reverseHistoryOrder()}>Reverse history order</button>
        </div>
      </div>
    );
  }
}
```

ですが、この方法では以下の点で不具合が生じています。（他にもあるが割愛）

- 「Go to game start」ボタンが常に最初に表示されている
- 「Go to move」の番号が逆転しない
- 逆順にした際にゲームデータがリセットされる
- ゲームを続けた際、`history`の最後に`move`が追加される


これらの不具合は、`Go to ～`ボタンの表示が`history.map`のインデックス（変数名は`move`）に基づいて行われているためです。
解消するには、`Game`コンポーネントで現状「昇順」と「降順」のどちらで表示されているかを管理する必要があります。

まず、`Game`コンポーネントの`state`に`isAscendingOrder`の真偽値を追加します。

```diff:jsx
// 省略
class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      history: [
        {
          squares: Array(9).fill(null),
          location: {
            col: null,
            row: null,
          },
        }
      ],
      stepNumber: 0,
+     isAscendingOrder: true,
      xIsNext: true
    };
  }
  // 省略
}
```

次に、`reverseHistoryOrder`メソッド内では`isAscendingOrder`を反転させます。

```diff:jsx
// 省略
class Game extends React.Component {
  // 省略
  reverseHistoryOrder() {
    this.setState({
-     history: this.state.history.slice().reverse(),
+     isAscendingOrder: !this.state.isAscendingOrder,
    });
  }
  // 省略
}
```

これで`history`の並び順を管理できるようになりましたが、このままでは表示が変わりません。
`Game`コンポーネントの`render`メソッドを修正します。

```diff:jsx
// 省略
class Game extends React.Component {
  // 省略
  render() {
-   const history = this.state.history;
    // 逆順の場合はthis.state.history配列のコピーを反転させる
    // これにより、this.state.historyはつねに昇順のデータを保持し続ける
+   const history = this.state.isAscendingOrder ? this.state.history : this.state.history.slice().reverse();
    const current = history[this.state.stepNumber];
    const winner = calculateWinner(current.squares);

    const moves = history.map((step, move) => {
      // 逆順の場合はインデックスを反転させる
+     const moveIndex = this.state.isAscendingOrder ? move : history.length - 1 - move;
-     const desc = move ?
+     const desc = moveIndex ?
-       'Go to move #' + move + '(' + step.location.col + ', ' + step.location.row + ')' :
+       'Go to move #' + moveIndex + '(' + step.location.col + ', ' + step.location.row + ')' :
        'Go to game start';
      return (
-       <li key={move}>
+       <li key={moveIndex}>
          <button
-           onClick={() => this.jumpTo(move)}
+           onClick={() => this.jumpTo(moveIndex)}
            className={move === currentStepNumber ? 'text-bold' : ''}
          >
            {desc}
          </button>
        </li>
      );
    });
    // 省略
  }
}
// 省略
```

ポイントは（コメントにも書いていますが）**`this.state.history`配列のコピーを反転させる**ことです。
[チュートリアルの「イミュータビリティは何故重要なのか」項目](https://ja.reactjs.org/tutorial/tutorial.html#why-immutability-is-important)にも記載されていますが、
`this.state.history`はつねに昇順のデータを保持し、あくまで表示のタイミングのみ反転した履歴を扱うことで、
`Game`コンポーネントの`handleClick`メソッドなどが修正不要となります。

ただし、このままでは画像のように、順序を入れ替えた場合に「現在選択されているアイテム」がずれてしまうバグがあります。
![react-tutorial-improve-4-bad.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205862/7217930c-46f6-f55e-022b-de034c1e7be7.png)
`#3`を選択していたのに、反転後に`#1`が選択されてしまっている

ですので、「現在選択されているアイテム」は降順の場合に反転させる必要があります。

```diff:jsx
// 省略
class Game extends React.Component {
  // 省略
  render() {
    // 逆順の場合はthis.state.history配列のコピーを反転させる
    // これにより、this.state.historyはつねに昇順のデータを保持し続ける
    const history = this.state.isAscendingOrder ? this.state.history : this.state.history.slice().reverse();
    // 現在選択されているアイテムのインデックスを逆順の場合に反転させる
+   const currentStepNumber = this.state.isAscendingOrder ? this.state.stepNumber : history.length - 1 - this.state.stepNumber;
-   const current = history[this.state.stepNumber];
+   const current = history[currentStepNumber];
    const winner = calculateWinner(current.squares);

    const moves = history.map((step, move) => {
      // 逆順の場合はインデックスを反転させる
      const moveIndex = this.state.isAscendingOrder ? move : history.length - 1 - move;
      const desc = moveIndex ?
        'Go to move #' + moveIndex + '(' + step.location.col + ', ' + step.location.row + ')' :
        'Go to game start';
      return (
        <li key={moveIndex}>
          <button
            onClick={() => this.jumpTo(moveIndex)}
-           className={move === this.state.stepNumber ? 'text-bold' : ''}
+           className={move === currentStepNumber ? 'text-bold' : ''}
          >
            {desc}
          </button>
        </li>
      );
    });
    // 省略
  }
}
// 省略
```

これで完成です。

<details><summary>【クリックで展開】ここまでのコードは以下になります。（[CodePenはこちら](https://codepen.io/ExoticToybox/pen/mdJKwqb)）</summary><div>

```html:html(変更なし)
<div id="errors" style="
  background: #c00;
  color: #fff;
  display: none;
  margin: -20px -20px 20px;
  padding: 20px;
  white-space: pre-wrap;
"></div>
<div id="root"></div>
<script>
window.addEventListener('mousedown', function(e) {
  document.body.classList.add('mouse-navigation');
  document.body.classList.remove('kbd-navigation');
});
window.addEventListener('keydown', function(e) {
  if (e.keyCode === 9) {
    document.body.classList.add('kbd-navigation');
    document.body.classList.remove('mouse-navigation');
  }
});
window.addEventListener('click', function(e) {
  if (e.target.tagName === 'A' && e.target.getAttribute('href') === '#') {
    e.preventDefault();
  }
});
window.onerror = function(message, source, line, col, error) {
  var text = error ? error.stack || error : message + ' (at ' + source + ':' + line + ':' + col + ')';
  errors.textContent += text + '\n';
  errors.style.display = '';
};
console.error = (function(old) {
  return function error() {
    errors.textContent += Array.prototype.slice.call(arguments).join(' ') + '\n';
    errors.style.display = '';
    old.apply(this, arguments);
  }
})(console.error);
</script>
```
```css: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;
}

.text-bold {
  font-weight: bold;
}
```
```jsx:jsx
function Square(props) {
  return (
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  );
}

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

  render() {
    const rows = [0, 1, 2];
    const cols = [0, 1, 2];
    return (
      <div>
        {rows.map(row => {
          return (
            <div
              className="board-row"
              key={row}
            >
              {cols.map(col => this.renderSquare(row * 3 + col))}
            </div>
          );
        })}
      </div>
    );
  }
}

class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      history: [
        {
          squares: Array(9).fill(null),
          location: {
            col: null,
            row: null,
          },
        }
      ],
      stepNumber: 0,
      isAscendingOrder: true,
      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,
          location: {
            col: i % 3,
            row: Math.trunc(i / 3),
          },
        }
      ]),
      stepNumber: history.length,
      xIsNext: !this.state.xIsNext
    });
  }

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

  reverseHistoryOrder() {
    this.setState({
      isAscendingOrder: !this.state.isAscendingOrder,
    });
  }

  render() {
    // 逆順の場合はthis.state.history配列のコピーを反転させる
    // これにより、this.state.historyはつねに昇順のデータを保持し続ける
    const history = this.state.isAscendingOrder ? this.state.history : this.state.history.slice().reverse();
    const currentStepNumber = this.state.isAscendingOrder ? this.state.stepNumber : history.length - 1 - this.state.stepNumber;
    const current = history[currentStepNumber];
    const winner = calculateWinner(current.squares);

    const moves = history.map((step, move) => {
      // 逆順の場合はインデックスを反転させる
      const moveIndex = this.state.isAscendingOrder ? move : history.length - 1 - move;
      const desc = moveIndex ?
        'Go to move #' + moveIndex + '(' + step.location.col + ', ' + step.location.row + ')' :
        'Go to game start';
      return (
        <li key={moveIndex}>
          <button
            onClick={() => this.jumpTo(moveIndex)}
            className={move === currentStepNumber ? 'text-bold' : ''}
          >
            {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>
          <button onClick={() => this.reverseHistoryOrder()}>Reverse history order</button>
        </div>
      </div>
    );
  }
}

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

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

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;
}
```
</div></details>

### 5. どちらかが勝利した際に、勝利につながった3つのマス目をハイライトする。
イメージはこんな感じ。
![react-tutorial-improve-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205862/77afb50f-6484-56ba-2784-a0c2fe82ee10.png)

こちらはまず`calculateWinner`関数を修正する必要がありそうです。
現状の`calculateWinner`関数は勝者（`X` or `O` or `null`）を返却していますが、
勝者がいる場合はそのパターンも一緒にしたオブジェクトを返却するようにしましょう。

```diff: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 {
+       winner: squares[a],
+       causedWinCells: lines[i],
+     };
    }
  }
- return null;
+ return {
+   winner: null,
+   causedWinCells: [],
+ };
}
```

さらに、`calculateWinner`関数の呼び出し元も修正します。

```diff:jsx
// 省略
class Game extends React.Component {
  // 省略
  handleClick(i) {
    const history = this.state.history.slice(0, this.state.stepNumber + 1);
    const current = history[history.length - 1];
    const squares = current.squares.slice();
+   const winInfo = calculateWinner(squares);
-   if (calculateWinner(squares) || squares[i]) {
+   if (winInfo.winner || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext ? "X" : "O";
    // 省略
  }

  // 省略
  render() {
    // 逆順の場合はthis.state.history配列のコピーを反転させる
    // これにより、this.state.historyはつねに昇順のデータを保持し続ける
    const history = this.state.isAscendingOrder ? this.state.history : this.state.history.slice().reverse();
    // 現在選択されているアイテムのインデックスを逆順の場合に反転させる
    const currentStepNumber = this.state.isAscendingOrder ? this.state.stepNumber : history.length - 1 - this.state.stepNumber;
    const current = history[currentStepNumber];
-   const winner = calculateWinner(current.squares);
+   const winInfo = calculateWinner(current.squares);
    // 省略
    let status;
-   if (winner) {
+   if (winInfo.winner) {
-     status = "Winner: " + winner;
+     status = "Winner: " + winInfo.winner;
    } else {
      status = "Next player: " + (this.state.xIsNext ? "X" : "O");
    }
    // 省略
  }
}
// 省略
```

これで今まで通りの表示ができるようになりました。
次はハイライト部分を作成します。
まず、ハイライトされた際に適用されるCSSを記述します。~~色は適当~~

```css:css
/* 省略 */
.square.caused-win {
  background: #fff799;
}
```

最終的に`<button class="square caused-win"></button>`を生成するのは`Square`コンポーネントですので、
真偽値が渡される前提で修正します。

```diff:jsx
function Square(props) {
  return (
-   <button className="square" onClick={props.onClick}>
+   <button
+     className={'square' + (props.causedWin ? ' caused-win' : '')}
+     onClick={props.onClick}
+   >
      {props.value}
    </button>
  );
}
// 省略
```
※読みやすくするため、`className`と`onClick`の両プロパティをそれぞれ独立した行に配置
`' caused-win'`の部分で、最初にスペースが入ることに気を付けてください。

`Square`コンポーネントの呼び出し元である`Board`コンポーネントの`renderSquare`メソッドも修正します。

```diff:jsx
// 省略
class Board extends React.Component {
- renderSquare(i) {
+ renderSquare(i, causedWin) {
    return (
      <Square
        value={this.props.squares[i]}
        onClick={() => this.props.onClick(i)}
        key={i}
+       causedWin={causedWin}
      />
    );
  }
  // 省略
}
// 省略
```

`Board`コンポーネントの`renderSquare`メソッドは`Board`コンポーネントの`render`メソッドから呼び出されており、
ここで初めてセル番号（`Square`コンポーネントの`key`）を生成しています。
ですので、同時にそのセルが勝利につながったかどうかを判定します。

```diff:jsx
// 省略
class Board extends React.Component {
  // 省略
  render() {
    const rows = [0, 1, 2];
    const cols = [0, 1, 2];
    return (
      <div>
        {rows.map(row => {
          return (
            <div
              className="board-row"
              key={row}
            >
-             {cols.map(col => this.renderSquare(row * 3 + col))}
+             {cols.map(col => {
+               const cell = row * 3 + col;
+               const causedWin = this.props.causedWinCells.includes(cell);
+               return this.renderSquare(cell, causedWin);
              })}
            </div>
          );
        })}
      </div>
    );
  }
}
// 省略
```

あとは`Game`コンポーネントから`Board`コンポーネントに`causedWinCells`を`props`で渡せば完成です。

```diff:jsx
// 省略
class Game extends React.Component {
  // 省略
  render() {
    // 省略
    return (
      <div className="game">
        <div className="game-board">
          <Board
            squares={current.squares}
            onClick={i => this.handleClick(i)}
+           causedWinCells={winInfo.causedWinCells}
          />
        </div>
        <div className="game-info">
          <div>{status}</div>
          <ol>{moves}</ol>
          <button onClick={() => this.reverseHistoryOrder()}>Reverse history order</button>
        </div>
      </div>
    );
  }
}
// 省略
```

<details><summary>【クリックで展開】ここまでのコードは以下になります。（[CodePenはこちら](https://codepen.io/ExoticToybox/pen/abOKyoM)）</summary><div>

```html:html(変更なし)
<div id="errors" style="
  background: #c00;
  color: #fff;
  display: none;
  margin: -20px -20px 20px;
  padding: 20px;
  white-space: pre-wrap;
"></div>
<div id="root"></div>
<script>
window.addEventListener('mousedown', function(e) {
  document.body.classList.add('mouse-navigation');
  document.body.classList.remove('kbd-navigation');
});
window.addEventListener('keydown', function(e) {
  if (e.keyCode === 9) {
    document.body.classList.add('kbd-navigation');
    document.body.classList.remove('mouse-navigation');
  }
});
window.addEventListener('click', function(e) {
  if (e.target.tagName === 'A' && e.target.getAttribute('href') === '#') {
    e.preventDefault();
  }
});
window.onerror = function(message, source, line, col, error) {
  var text = error ? error.stack || error : message + ' (at ' + source + ':' + line + ':' + col + ')';
  errors.textContent += text + '\n';
  errors.style.display = '';
};
console.error = (function(old) {
  return function error() {
    errors.textContent += Array.prototype.slice.call(arguments).join(' ') + '\n';
    errors.style.display = '';
    old.apply(this, arguments);
  }
})(console.error);
</script>
```
```css: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;
}

.text-bold {
  font-weight: bold;
}

.square.caused-win {
  background: #fff799;
}
```
```jsx:jsx
function Square(props) {
  return (
    <button
      className={'square' + (props.causedWin ? ' caused-win' : '')}
      onClick={props.onClick}
    >
      {props.value}
    </button>
  );
}

class Board extends React.Component {
  renderSquare(i, causedWin) {
    return (
      <Square
        value={this.props.squares[i]}
        onClick={() => this.props.onClick(i)}
        key={i}
        causedWin={causedWin}
      />
    );
  }

  render() {
    const rows = [0, 1, 2];
    const cols = [0, 1, 2];
    return (
      <div>
        {rows.map(row => {
          return (
            <div
              className="board-row"
              key={row}
            >
              {cols.map(col => {
                const cell = row * 3 + col;
                const causedWin = this.props.causedWinCells.includes(cell);
                return this.renderSquare(cell, causedWin);
              })}
            </div>
          );
        })}
      </div>
    );
  }
}

class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      history: [
        {
          squares: Array(9).fill(null),
          location: {
            col: null,
            row: null,
          },
        }
      ],
      stepNumber: 0,
      isAscendingOrder: true,
      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();
    const winInfo = calculateWinner(squares);
    if (winInfo.winner || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext ? "X" : "O";
    this.setState({
      history: history.concat([
        {
          squares: squares,
          location: {
            col: i % 3,
            row: Math.trunc(i / 3),
          },
        }
      ]),
      stepNumber: history.length,
      xIsNext: !this.state.xIsNext
    });
  }

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

  reverseHistoryOrder() {
    this.setState({
      isAscendingOrder: !this.state.isAscendingOrder,
    });
  }

  render() {
    // 逆順の場合はthis.state.history配列のコピーを反転させる
    // これにより、this.state.historyはつねに昇順のデータを保持し続ける
    const history = this.state.isAscendingOrder ? this.state.history : this.state.history.slice().reverse();
    // 現在選択されているアイテムのインデックスを逆順の場合に反転させる
    const currentStepNumber = this.state.isAscendingOrder ? this.state.stepNumber : history.length - 1 - this.state.stepNumber;
    const current = history[currentStepNumber];
    const winInfo = calculateWinner(current.squares);

    const moves = history.map((step, move) => {
      // 逆順の場合はインデックスを反転させる
      const moveIndex = this.state.isAscendingOrder ? move : history.length - 1 - move;
      const desc = moveIndex ?
        'Go to move #' + moveIndex + '(' + step.location.col + ', ' + step.location.row + ')' :
        'Go to game start';
      return (
        <li key={moveIndex}>
          <button
            onClick={() => this.jumpTo(moveIndex)}
            className={move === currentStepNumber ? 'text-bold' : ''}
          >
            {desc}
          </button>
        </li>
      );
    });

    let status;
    if (winInfo.winner) {
      status = "Winner: " + winInfo.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)}
            causedWinCells={winInfo.causedWinCells}
          />
        </div>
        <div className="game-info">
          <div>{status}</div>
          <ol>{moves}</ol>
          <button onClick={() => this.reverseHistoryOrder()}>Reverse history order</button>
        </div>
      </div>
    );
  }
}

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

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

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 {
        winner: squares[a],
        causedWinCells: lines[i],
      };
    }
  }
  return {
    winner: null,
    causedWinCells: [],
  };
}
```
</div></details>


### 6. どちらも勝利しなかった場合、結果が引き分けになったというメッセージを表示する。
イメージはこんな感じ。
~~これ一番簡単なんじゃないか？~~
![react-tutorial-improve-6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205862/a0e70e35-4e44-9030-a7a5-297b5074f3e7.png)

この部分に表示しているテキストは`Game`コンポーネントの`render`メソッド内にて`status`という変数で管理されています。

```jsx:jsx
// 省略
class Game extends React.Component {
  // 省略
  render() {
    // 省略
    let status;
    if (winInfo.winner) {
      status = "Winner: " + winInfo.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)}
            causedWinCells={winInfo.causedWinCells}
          />
        </div>
        <div className="game-info">
          <div>{status}</div>
          <ol>{moves}</ol>
          <button onClick={() => this.reverseHistoryOrder()}>Reverse history order</button>
        </div>
      </div>
    );
  }
}
// 省略
```

ここに新しい条件を追加します。
`引き分け`というステータスは、「`勝者がいない`かつ`すべてのセルがnullではない（"X"か"O"である）`」です。

```diff:jsx
// 省略
class Game extends React.Component {
  // 省略
  render() {
    // 省略
    let status;
    if (winInfo.winner) {
      status = "Winner: " + winInfo.winner;
+   } else if (!current.squares.includes(null)) {
+     status = "Draw";
    } else {
      status = "Next player: " + (this.state.xIsNext ? "X" : "O");
    }
    // 省略
  }
}
// 省略
```

これで完成です。

<details><summary>【クリックで展開】ここまでのコードは以下になります。（[CodePenはこちら](https://codepen.io/ExoticToybox/pen/gOpKxwg)）</summary><div>

```html:html(変更なし)
<div id="errors" style="
  background: #c00;
  color: #fff;
  display: none;
  margin: -20px -20px 20px;
  padding: 20px;
  white-space: pre-wrap;
"></div>
<div id="root"></div>
<script>
window.addEventListener('mousedown', function(e) {
  document.body.classList.add('mouse-navigation');
  document.body.classList.remove('kbd-navigation');
});
window.addEventListener('keydown', function(e) {
  if (e.keyCode === 9) {
    document.body.classList.add('kbd-navigation');
    document.body.classList.remove('mouse-navigation');
  }
});
window.addEventListener('click', function(e) {
  if (e.target.tagName === 'A' && e.target.getAttribute('href') === '#') {
    e.preventDefault();
  }
});
window.onerror = function(message, source, line, col, error) {
  var text = error ? error.stack || error : message + ' (at ' + source + ':' + line + ':' + col + ')';
  errors.textContent += text + '\n';
  errors.style.display = '';
};
console.error = (function(old) {
  return function error() {
    errors.textContent += Array.prototype.slice.call(arguments).join(' ') + '\n';
    errors.style.display = '';
    old.apply(this, arguments);
  }
})(console.error);
</script>
```
```css: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;
}

.text-bold {
  font-weight: bold;
}

.square.caused-win {
  background: #fff799;
}
```
```jsx:jsx
function Square(props) {
  return (
    <button
      className={'square' + (props.causedWin ? ' caused-win' : '')}
      onClick={props.onClick}
    >
      {props.value}
    </button>
  );
}

class Board extends React.Component {
  renderSquare(i, causedWin) {
    return (
      <Square
        value={this.props.squares[i]}
        onClick={() => this.props.onClick(i)}
        key={i}
        causedWin={causedWin}
      />
    );
  }

  render() {
    const rows = [0, 1, 2];
    const cols = [0, 1, 2];
    return (
      <div>
        {rows.map(row => {
          return (
            <div
              className="board-row"
              key={row}
            >
              {cols.map(col => {
                const cell = row * 3 + col;
                const causedWin = this.props.causedWinCells.includes(cell);
                return this.renderSquare(cell, causedWin);
              })}
            </div>
          );
        })}
      </div>
    );
  }
}

class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      history: [
        {
          squares: Array(9).fill(null),
          location: {
            col: null,
            row: null,
          },
        }
      ],
      stepNumber: 0,
      isAscendingOrder: true,
      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();
    const winInfo = calculateWinner(squares);
    if (winInfo.winner || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext ? "X" : "O";
    this.setState({
      history: history.concat([
        {
          squares: squares,
          location: {
            col: i % 3,
            row: Math.trunc(i / 3),
          },
        }
      ]),
      stepNumber: history.length,
      xIsNext: !this.state.xIsNext
    });
  }

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

  reverseHistoryOrder() {
    this.setState({
      isAscendingOrder: !this.state.isAscendingOrder,
    });
  }

  render() {
    // 逆順の場合はthis.state.history配列のコピーを反転させる
    // これにより、this.state.historyはつねに昇順のデータを保持し続ける
    const history = this.state.isAscendingOrder ? this.state.history : this.state.history.slice().reverse();
    // 現在選択されているアイテムのインデックスを逆順の場合に反転させる
    const currentStepNumber = this.state.isAscendingOrder ? this.state.stepNumber : history.length - 1 - this.state.stepNumber;
    const current = history[currentStepNumber];
    const winInfo = calculateWinner(current.squares);

    const moves = history.map((step, move) => {
      // 逆順の場合はインデックスを反転させる
      const moveIndex = this.state.isAscendingOrder ? move : history.length - 1 - move;
      const desc = moveIndex ?
        'Go to move #' + moveIndex + '(' + step.location.col + ', ' + step.location.row + ')' :
        'Go to game start';
      return (
        <li key={moveIndex}>
          <button
            onClick={() => this.jumpTo(moveIndex)}
            className={move === currentStepNumber ? 'text-bold' : ''}
          >
            {desc}
          </button>
        </li>
      );
    });

    let status;
    if (winInfo.winner) {
      status = "Winner: " + winInfo.winner;
    } else if (!current.squares.includes(null)) {
      status = "Draw";
    } 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)}
            causedWinCells={winInfo.causedWinCells}
          />
        </div>
        <div className="game-info">
          <div>{status}</div>
          <ol>{moves}</ol>
          <button onClick={() => this.reverseHistoryOrder()}>Reverse history order</button>
        </div>
      </div>
    );
  }
}

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

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

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 {
        winner: squares[a],
        causedWinCells: lines[i],
      };
    }
  }
  return {
    winner: null,
    causedWinCells: [],
  };
}
```
</div></details>

## 後書き
できるだけチュートリアルの構文に則った書き方をしてみました。
最後の問題だけどうしても簡単すぎて、何か見落としているのではないかと思ってしまいます。
バグや、「ここはこうしたほうがいいんじゃない？」というようなポイントがあればコメントいただければ嬉しいです。

~~あと、この記事ではコードブロックのシンタックスハイライトを`js`にしているのですが、QiitaではJSX構文で赤くエラーの表示がされてしまいます。
こちらについても解決策をご存じの方がいらっしゃいましたらコメントお待ちしています。~~
→ @d0ne1s さんからコメントで指摘をいただき、シンタックスハイライトを`jsx`に修正しました。

次はこれをTypeScriptで書いてみたい。

## 参考
[React公式チュートリアル](https://ja.reactjs.org/tutorial/tutorial.html)
[React公式ガイド](https://ja.reactjs.org/docs/hello-world.html)
[React.js 実戦投入への道 - Qiita](https://qiita.com/icoxfog417/items/5d79b3336226aa51e30d)
[React における State と Props の違い - Qiita](https://qiita.com/kyrieleison/items/78b3295ff3f37969ab50)
[Reactのstate,props,componentの使い方を自分なりにまとめてみた - Qiita](https://qiita.com/sekikawa_a/items/8ab70f457ef73871419f)
[React.jsでループするには。 - Qiita](https://qiita.com/konojunya/items/cb026a2aa3df1837d587)