Reactのチュートリアル

# 最終目標
フロントエンドとバックエンドのDB排他を調べてみたい。

# 今回の目標
Tutorial: Intro to Reactを実施する
https://reactjs.org/tutorial/tutorial.html
https://mae.chab.in/archives/2943

# 環境
```
Ubuntu 18.04.2 LTS
node v8.10.0
npm 3.5.2
create-react-app v2.1.5
```
詳細は、https://qiita.com/y_ohr/items/8afd0852b24356059f2b 参照。

# Tutorial: Intro to React
ローカルでやります。

## react-app作成

```shell
$ create-react-app tutorial-react-app
$ cd tutorial-react-app/
```

## src配下削除

```shell
$ cd src/
$ rm -f *
$ cd ..
```

## src/index.css作成

```css:src/index.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;
}
```

## src/index.js作成

```js:src/index.js
class Square extends React.Component {
    render() {
        return (
            <button className="square">
                {/* TODO */}
            </button>
        );
    }
}

class Board extends React.Component {
    renderSquare(i) {
        return <Square />;
    }

    render() {
        const status = 'Next player: X';

        return (
            <div>
                <div className="status">{status}</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 {
    render() {
        return (
            <div className="game">
                <div className="game-board">
                    <Board />
                </div>
                <div className="game-info">
                    <div>{/* status */}</div>
                    <ol>{/* TODO */}</ol>
                </div>
            </div>
        );
    }
}

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

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

## index.jsの先頭に３行追加。ここからはdiffにて。

```diff:src/index.js
diff --git a/src/index.js b/src/index.js
index 9a06c07..70f7c67 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,3 +1,7 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import './index.css';
+
 class Square extends React.Component {
     render() {
         return (
```

## Passing Data Through Props

```diff:src/index.js
diff --git a/src/index.js b/src/index.js
index 70f7c67..43522c3 100644
--- a/src/index.js
+++ b/src/index.js
@@ -6,7 +6,7 @@ class Square extends React.Component {
     render() {
         return (
             <button className="square">
-                {/* TODO */}
+                {this.props.value}
             </button>
         );
     }
@@ -14,7 +14,7 @@ class Square extends React.Component {

 class Board extends React.Component {
     renderSquare(i) {
-        return <Square />;
+        return <Square value={i} />;
     }

     render() {
```

## Making an Interactive Component

```diff:src/index.js
diff --git a/src/index.js b/src/index.js
index 43522c3..b908669 100644
--- a/src/index.js
+++ b/src/index.js
@@ -3,10 +3,19 @@ import ReactDOM from 'react-dom';
 import './index.css';

 class Square extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {
+            value: null,
+        };
+    }
+
     render() {
         return (
-            <button className="square">
-                {this.props.value}
+            <button
+                className="square"
+                onClick={() => this.setState({ value: 'X' })}>
+                {this.state.value}
             </button>
         );
     }
```

## Lifting State Up

```diff:src/index.js
diff --git a/src/index.js b/src/index.js
index b908669..01c54da 100644
--- a/src/index.js
+++ b/src/index.js
@@ -3,27 +3,38 @@ import ReactDOM from 'react-dom';
 import './index.css';

 class Square extends React.Component {
-    constructor(props) {
-        super(props);
-        this.state = {
-            value: null,
-        };
-    }
-
     render() {
         return (
             <button
                 className="square"
-                onClick={() => this.setState({ value: 'X' })}>
-                {this.state.value}
+                onClick={() => this.props.onClick()}>
+                {this.props.value}
             </button>
         );
     }
 }

 class Board extends React.Component {
+    constructor() {
+        super();
+        this.state = {
+            squares: Array(9).fill(null),
+        };
+    }
+
+    handleClick(i) {
+        const squares = this.state.squares.slice();
+        squares[i] = 'X';
+        this.setState({ squares: squares });
+    }
+
     renderSquare(i) {
-        return <Square value={i} />;
+        return (
+            <Square
+                value={this.state.squares[i]}
+                onClick={() => this.handleClick(i)}
+            />
+        );
     }

     render() {
```

## Function Components

```diff:src/index.js
diff --git a/src/index.js b/src/index.js
index 01c54da..5f16f3d 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,16 +2,14 @@ import React from 'react';
 import ReactDOM from 'react-dom';
 import './index.css';

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

 class Board extends React.Component {
```

## Taking Turns

```diff:src/index.js
diff --git a/src/index.js b/src/index.js
index 5f16f3d..fa7e58a 100644
--- a/src/index.js
+++ b/src/index.js
@@ -17,13 +17,17 @@ class Board extends React.Component {
         super();
         this.state = {
             squares: Array(9).fill(null),
+            xIsNext: true,
         };
     }

     handleClick(i) {
         const squares = this.state.squares.slice();
-        squares[i] = 'X';
-        this.setState({ squares: squares });
+        squares[i] = this.state.xIsNext ? 'X' : 'O';
+        this.setState({
+            squares: squares,
+            xIsNext: !this.state.xIsNext,
+        });
     }

     renderSquare(i) {
@@ -36,7 +40,7 @@ class Board extends React.Component {
     }

     render() {
-        const status = 'Next player: X';
+        const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');

         return (
             <div>
```

## Declaring a Winner

```diff:src/index.js
diff --git a/src/index.js b/src/index.js
index fa7e58a..34c275d 100644
--- a/src/index.js
+++ b/src/index.js
@@ -23,6 +23,10 @@ class Board extends React.Component {

     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,
@@ -40,7 +44,13 @@ class Board extends React.Component {
     }

     render() {
-        const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
+        const winner = calculateWinner(this.state.squares);
+        let status;
+        if (winner) {
+            status = 'Winner: ' + winner;
+        } else {
+            status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
+        }

         return (
             <div>
@@ -87,3 +97,23 @@ 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;
+}
```

## Lifting State Up, Again

```diff:src/index.js
diff --git a/src/index.js b/src/index.js
index 34c275d..65a0010 100644
--- a/src/index.js
+++ b/src/index.js
@@ -13,48 +13,18 @@ function Square(props) {
 }

 class Board extends React.Component {
-    constructor() {
-        super();
-        this.state = {
-            squares: Array(9).fill(null),
-            xIsNext: true,
-        };
-    }
-
-    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,
-        });
-    }
-
     renderSquare(i) {
         return (
             <Square
-                value={this.state.squares[i]}
-                onClick={() => this.handleClick(i)}
+                value={this.props.squares[i]}
+                onClick={() => this.props.onClick(i)}
             />
         );
     }

     render() {
-        const winner = calculateWinner(this.state.squares);
-        let status;
-        if (winner) {
-            status = 'Winner: ' + winner;
-        } else {
-            status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
-        }
-
         return (
             <div>
-                <div className="status">{status}</div>
                 <div className="board-row">
                     {this.renderSquare(0)}
                     {this.renderSquare(1)}
@@ -76,14 +46,56 @@ class Board extends React.Component {
 }

 class Game extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {
+            history: [{
+                squares: Array(9).fill(null),
+            }],
+            xIsNext: true,
+        };
+    }
+
+    handleClick(i) {
+        const history = this.state.history;
+        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,
+            }]),
+            xIsNext: !this.state.xIsNext,
+        });
+    }
+
     render() {
+        const history = this.state.history;
+        const current = history[history.length - 1];
+        const winner = calculateWinner(current.squares);
+
+        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 />
+                    <Board
+                        squares={current.squares}
+                        onClick={(i) => this.handleClick(i)}
+                    />
+
                 </div>
                 <div className="game-info">
-                    <div>{/* status */}</div>
+                    <div>{status}</div>
                     <ol>{/* TODO */}</ol>
                 </div>
             </div>
```

## Showing the Past Moves

```diff:src/index.js
diff --git a/src/index.js b/src/index.js
index 65a0010..5afcc7d 100644
--- a/src/index.js
+++ b/src/index.js
@@ -78,6 +78,15 @@ class Game extends React.Component {
         const current = history[history.length - 1];
         const winner = calculateWinner(current.squares);

+        const moves = history.map((step, move) => {
+            const desc = move ? 'Go to move #' + move : 'Go to game start';
+            return (
+                <li>
+                    <button onClick={() => this.jumpTo(move)}>{desc}</button>
+                </li>
+            );
+        });
+
         let status;
         if (winner) {
             status = 'Winner: ' + winner;
@@ -96,7 +105,7 @@ class Game extends React.Component {
                 </div>
                 <div className="game-info">
                     <div>{status}</div>
-                    <ol>{/* TODO */}</ol>
+                    <ol>{moves}</ol>
                 </div>
             </div>
         );
```

## Implementing Time Travel

```diff:src/index.js
diff --git a/src/index.js b/src/index.js
index 5afcc7d..0412658 100644
--- a/src/index.js
+++ b/src/index.js
@@ -52,12 +52,13 @@ class Game extends React.Component {
             history: [{
                 squares: Array(9).fill(null),
             }],
+            stepNumber: 0,
             xIsNext: true,
         };
     }

     handleClick(i) {
-        const history = this.state.history;
+        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]) {
@@ -69,19 +70,27 @@ class Game extends React.Component {
             history: history.concat([{
                 squares: squares,
             }]),
+            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[history.length - 1];
+        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>
+                <li key={move}>
                     <button onClick={() => this.jumpTo(move)}>{desc}</button>
                 </li>
             );
```

# 感想
- 写経
- 思ったより時間がかかった＝充実したチュートリアルだった。
- inputで動的に仮想DOMが書き換わるようなプログラムを書いてみたい

以上