Reactのチュートリアル追加課題解いてみた

# はじめに
[Reactチュートリアル](https://ja.reactjs.org/tutorial/tutorial.html)の追加課題の解答と、私が解いていく中で詰まったところを記述する。ReactやJSに関する知識が拙い中解いたので、より良い解き方などがあると考えられるが、一例として参考にしていただきたい。(より良い解き方など指摘いただければ追記致します。)
コードはチュートリアル内の[最終結果](https://codepen.io/gaearon/pen/gWWZgR?editors=0010)を改変した。

## 1.履歴内のそれぞれの着手の位置を (col, row) というフォーマットで表示する。
### 考え方
着手の位置を履歴として残すために、```history```に新たな要素として追加する。その後```moves```に着手の位置も表示するように変更すればよい。

### 解答例
まず、```Game```クラスのコンストラクタにある```history```と盤面が動いた際の処理が書かれたメソッド```handleClick```内の```history```に```putState```をkeyとした着手の位置を保存するための要素を追加する(history自体ではなくhistory内にある辞書やhistoryに追加する辞書に対しての意)。
コンストラクタでは以下のように変更する(ここで定義しなくても動く)。

```Javascript
history: [
    {
        squares: Array(9).fill(null),
        putState: "(0, 0)"
    }
]
```
だが、```handleClick```メソッドでは着手の位置を与える必要がある。幸運なことに(当然だが)このメソッドは選択したマスの番号を引数として与えられているので、以下のように変更するだけでよい。

```Javascript
history: history.concat([
    {
        squares: squares,
        putState: ` (${i/3|0}, ${i%3})`
    }
])
```
JavaScriptのバージョンによっては文字列の埋め込みがこのようにできないので以下のようにする。

```Javascript
history: history.concat([
    {
        squares: squares,
        putState: " (" + (i/3|0).toString() + ", " + (i%3).toString() + ")"
    }
])
```
このようにして着手の位置を履歴として残せるようになったので最後に```moves```を変更し、着手の位置を表示させる。

```Javascript
const moves = history.map((step, move) => {
    const desc = move ?
        'Go to move #' + move + step["putState"]:
        'Go to game start';
    return (
        <li key={move}>
            <button onClick={() => this.jumpTo(move)}>{desc}</button>
        </li>
    );
});
```

## 2.着手履歴のリスト中で現在選択されているアイテムをボールドにする。
### 考え方
選択されている盤面は```Game```クラスのメンバ変数```StepNumber```に格納されている。また、着手履歴の表示に関する部分は```moves```に格納されている。この```moves```は着手履歴を保存している```history```を用いてrenderされる度に生成される。さらに表示したい盤面を着手履歴のリストから選択するたび```StepNumber```が更新され再度renderされる。つまり、選択のたびrenderされるのでmovesが生成されるときに```StepNumber```と一致する番目にあるアイテムをボールドするようにすれば良いと考えられる。
### 解答例
変更点は```moves```の定義部分のみ

```Javascript
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)}>
                {
                    move === this.state.stepNumber
                    ? <b>{desc}</b>
                    : desc
                }
            </button>
        </li>
    );
});
```
最初falseの時の```desc```を```{desc}```と書いていてerror({}が二重になるため)を出していたのでこの部分は注意したい。


## 3.Board でマス目を並べる部分を、ハードコーディングではなく 2 つのループを使用するように書き換える。
### 考え方
単純にリストを作って二重ループ回してリストにJSXを適宜格納し、ハードコーディング部分をリストに置き換える。
### 解答例
考え方通りコードを書く。

```Javascript
render() {
    const squareBoard = [];
    const row = 3;
    const col = 3;
    for(let i=0; i<row; i++){
        let rowBoard = [];
        for(let j=0;j<col; j++){
            rowBoard.push(this.renderSquare(j+3*i));
        }
        squareBoard.push(
            <div className="board-row">
                {rowBoard}
            </div>
        )
    }
    return (
        <div>
            {squareBoard}
        </div>
    );
}
```
これで上手くいくと考えていたが警告が出る。
```Warning: Each child in a list should have a unique "key" prop.
```これはkeyに関する警告でチュートリアル中にも触れられていたものである。keyを設定することで[VirtualDOM](https://stackoverflow.com/questions/21965738/what-is-virtual-dom)のdiffから実際のDOMに反映させるときの変更を最小限にすることができる。変更を施すと以下のようになる。

```Javascript
class Board extends React.Component {
    renderSquare(i) {
        return (
            <Square
                value={this.props.squares[i]}
                onClick={() => this.props.onClick(i)}
                key={"square-"+i.toString()}
            />
        );
    }

    render() {
        const squareBoard = [];
        const row = 3;
        const col = 3;
        for(let i=0; i<row; i++){
            let rowBoard = [];
            for(let j=0;j<col; j++){
                rowBoard.push(this.renderSquare(j+3*i));
            }
            squareBoard.push(
                <div className="board-row" key={"row-"+i.toString()}>
                    {rowBoard}
                </div>
            )
        }
        return (
            <div>
                {squareBoard}
            </div>
        );
    }
}
```
ここでは```"row-"+i.toString()```としているがiだけでも問題ない(```Square```も同様)。

## 4.着手履歴のリストを昇順・降順いずれでも並べかえられるよう、トグルボタンを追加する。
私はトグルボタンそのものを実装することは本質ではなく、コードが複雑になると考えたので単純にボタンで実装した(トグルボタンにしたい方はトグルボタンのコンポーネントを作成してボタンのタグをトグルボタンのタグに変更することでできる)。
### 考え方
着手履歴は、```Game```クラスのメンバ変数```history```に格納されており、```moves```によって実装されている。```moves```は```history```から履歴を一手ずつ取ってきてJSXにしたものをリストにしている。```history```は常に昇順であるためこれから作られた```moves```もまた常に昇順となっている。つまり降順で表示したいときは```moves```を逆順にすると良いと考えられる。
### 解答例
昇順、降順ボタンを任意の位置(ここでは着手履歴の上)に設定し、それを押すことで新たに追加する```Game```のメンバ変数```ascending```の値を変える。```ascending```は昇順でtrue、降順でfalseとなるようにする。```ascending```の値によってrenderの直前に```moves```を逆順にするかどうかを決める。

```Javascript
constructor(props) {
    super(props);
    this.state = {
        history: [
            {
                squares: Array(9).fill(null)
            }
        ],
        stepNumber: 0,
        xIsNext: true,
        ascending: true,
    };
}
```
```Javascript
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 (
            <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");
    }

    if(!this.state.ascending) moves.reverse();

    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>
                <button onClick={()=>this.setState({ascending: true})}>昇順</button>
                <button onClick={()=>this.setState({ascending: false})}>降順</button>
                <ol>{moves}</ol>
            </div>
        </div>
    );
}
```
一つ目は```Game```クラスのコンストラクタ部分で、二つ目は```Game```クラスのrender部分である。トグルボタンでは押したときに```ascending```をtrueやfalseを切り替えるように実装してやれば良い。

```JaveScript
<ToggleButton onClick={()=>this.setState({ascending: !ascending})}/>
```

## 5.どちらかが勝利した際に、勝利につながった 3 つのマス目をハイライトする。
### 考え方
勝敗が決定したときにハイライトさせるので勝敗を確認する```calculateWinner```関数と勝敗によって値が変わる```Game```クラス内の```status```を定義している部分をうまく変更すれば良いと考えられる。まず勝敗につながった3マスをどのように抜き出すかを考える。```calculateWinner```関数では勝利につながる可能性のある3マスの組を全列挙しそのうちの一つでも3マス全てが同じ記号であればその記号を返し、1つもなければnullを返している。つまり、```calculateWinner```関数では勝敗を決まった時勝利につながった3マスを特定することができ、返り値にその3マスも返すようにすることが可能であると考えられる。次に抜き出した情報をもとにハイライトを行う方法を考える。勝敗が決まった時のみこの処理を行うのでハイライトを行う機能は```status```を定義する場面で行う。勝敗が決まった時の処理で```current.squares```の先ほど抜き出した3マスをハイライトする機能を追加すると良いと考えられる。

### 解答例
まず、```clulculateWinner```関数の返り値を既存のものと勝利につながった3マスを合わせたものへと変更する。

```Javascript
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], lines[i]];
        }
    }
    return [null, []];
}
```
返り値を変えたことによって```handleClick```メソッドに少々の変更を行う。

```Javascript
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)[0] || squares[i]) {//ここだよ
        return;
    }
    squares[i] = this.state.xIsNext ? "X" : "O";
    this.setState({
        history: history.concat([
            {
                squares: squares
            }
        ]),
        stepNumber: history.length,
        xIsNext: !this.state.xIsNext
    });
}
```
最後に```status```内の処理に一手間加える。

```JavaScript
let status;
if (winner[0]) {
    status = "Winner: " + winner[0];
    for(let i in winner[1]) current.squares[winner[1][i]] = <font color="#F00">{current.squares[winner[1][i]]}</font>;
} else {
    status = "Next player: " + (this.state.xIsNext ? "X" : "O");
}
```
ハイライトは3つのマスを赤色にすることにした(これは何でもいい)。
しかし、この方法だと勝利につながったマスが5つの時であっても3つしかハイライトされない。このようなケースは通常人間同士が真剣に行ったときに出ることはないが、気持ち悪いので5つともハイライトされるようにする。変更したのは```calculateWinner```関数のみ。

```Javascript
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]
    ];
    let winner = null;
    let winLines = [];
    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]) {
            winLines = winLines.concat(lines[i]);
            winner = squares[a];
        }
    }
    return [winner, winLines];
}
```

## 6.どちらも勝利しなかった場合、結果が引き分けになったというメッセージを表示する。
### 考え方
引き分けのメッセージは```status```に新たな分岐を作ることによって、引き分けの判別は```calculateWinner```関数で引き換えの処理を加えればよいと考えられる。また、引き分けは勝敗が決まってない時の```squares```の中身にnullがあるかないかで判別することができる。

###解答例
まず、```status```は```calculateWinner```関数の返り値に依存しているので、```calculateWinner```関数を変更する。この関数は```handleClick```メソッドにも使われており、それに影響が出ないような形で変更したい。つまり、bool値がtrueとなるような値になるようにする。

```Javascript
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];
        }
    }

    if (squares.indexOf(null) === -1) return "draw";

    return null;
}
```
最後に```status```の定義部分を変更する。引き分けになった時の表示はDrawにした。

```Javascript
let status;
if (winner === "draw") {
    status = "Draw";
} else if (winner) {
    status = "Winner: " + winner;
} else {
    status = "Next player: " + (this.state.xIsNext ? "X" : "O");
}
```

# おわりに(全体コード)
追加課題を全て終えた後は[このような](https://github.com/KokiSakano/react-tutorial/blob/master/src/index.js)コードになった。より良い書き方や書き方の問題などがあれば追記するので教えていただきたい。