React TutorialでComponentを別ファイルに分けてやってみた


# React Tutorial
Reactの公式チュートリアルを日本語訳してくださったありがたいサイトがある([ReactTutorial日本語訳](https://mae.chab.in/archives/2943))

公式Tutorialでは[CodePen](https://codepen.io/ericnakagawa/pen/vXpjwZ?editors=0010)のコードを利用して進めろとあるが、Componentが複数定義され、長くて見づらくなってしまうと思い、Componentを別ファイルに分けて行った。
環境自体もCodePenを用いていないので自身で環境構築を行った。
[最終GOAL(CodePen)](https://codepen.io/gaearon/pen/gWWZgR?editors=0010)

## 環境構築
### アプリケーション用ディレクトリ作成
```console
$mkdir react-tutorial
$cd react-tutorial
$npm init -y
```
### npmパッケージのインストール
```console
$npm install react react-dom
$npm install webpack webpack-dev-server --save-dev
$npm install babel-cli babel-loader babel-preset-env babel-preset-react --save-dev
$npm install eslint eslint-loader eslint-plugin-react --save-dev
$npm install css-loader style-loader babel-loader --save-dev
```

### 作業ディレクトリ作成(html,css,js)
```sh
mkdir src public
```

### 設定ファイルの作成
```json:.babelrc
{
  "presets": ["env", "react"]
}
```

```.eslintrc.json
{
    "env": {
        "browser": true,
        "es6": true
    },
    "parserOptions": {
        "sourceType": "module",
        "ecmaFeatures": {
            "experimentalObjectRestSpread": true,
            "jsx": true
        }
    },
    "extends": ["eslint:recommended", "plugin:react/recommended"],
    "plugins": ["react"],
    "rules": {
        "no-console": "off"
    }
}

```

```webpack.config.js
module.exports = {
  entry: {
    app: "./src/index.js"
  },
  output: {
    path: __dirname + '/public/js',
    filename: "[name].js"
  },
    devServer: {
    contentBase: __dirname + '/public',
    port: 8080,
    publicPath: '/js/'
  },
  devtool: "#inline-source-map",
  module: {
    rules: [{
      test: /\.js$/,
      enforce: "pre",
      exclude: /node_modules/,
      loader: "eslint-loader"
    }, {
      test: /\.css$/,
      loader: ["style-loader","css-loader"]
    }, {
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'babel-loader'
     }]
  }
};

```

```punlic/index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>React App</title>
</head>
<body>
  <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>
  <script type="text/javascript" src="js/app.js" charset="utf-8"></script>
</body>
</html>
```

## index.js
```jsx:index.js
import './index.css'
import React from 'react'
import ReactDOM from 'react-dom'
import Game from './Game'

ReactDOM.render(<Game />, document.getElementById('root'))
```
## Game.js
```jsx:Game.js
import React, { Component } from 'react';
import Board from './Board'

class Game extends Component {
  constructor() {
    super();
    this.state = {
      history: [
        {
          squares: Array(9).fill(null)
        }
      ],
      stepNumber: 0,
      xIsNext: true
    };
  }

  handleClick(i) {
    const history = this.state.history.slice(0, this.state.stepNumber + 1);
    var 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,
      stepNumber: history.length
    });
  }

  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 ? 'Move #' + move : 'Game start';
      return (
        <li key={move}>
          <a href="#" onClick={() => this.jumpTo(move)}>{desc}</a>
        </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>
    );
  }
}

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

export default Game

```

## Board.js
```jsx:Board.js
import React, { Component } from 'react'
import PropTypes from 'prop-types';
import Square from './Square'

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

  render() {
    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>
    );
  }
}
Board.propTypes = {
  squares: PropTypes.array,
  onClick: PropTypes.func  
}

export default Board
```

## Square.js
```jsx:Square.js
import React from 'react'
import PropTypes from 'prop-types';

function Square(props){
  return(
    <button className="square" onClick={() => props.onClick()}>
      {props.value}
    </button>
  );
}
Square.propTypes = {
  value: PropTypes.string,
  onClick: PropTypes.func
}

export default Square
```

## メモ
はじめはclassでSquareコンポーネントが定義されていたが、コンストラクタが必要ないこととrenderメソッドのみからなるコンポーネントタイプであるためpropsを受け取り、何をレンダリングすべきかを返す関数に変更したほうがいいっぽい。

### PropType
PropTypeは公式Tutorialに記載されていないが、PropTypeを利用すると実行時に渡されたパラメータが正しいかどうかチェックしてくれる。

|型|PropType|
|:--:|:--:|
|配列|PropTypes.array|
|論理値|PropTypes.bool|
|関数|PropTypes.func|
|数値|PropTypes.number|
|オブジェクト|PropTypes.object|
|文字列|PropTypes.string|

## 実行方法
```console
$npm start
```

## 実行結果

![スクリーンショット 2017-11-28 3.12.36.png](https://qiita-image-store.s3.amazonaws.com/0/154845/a1a75046-de1d-2da4-a9aa-bb3b451913d0.png "スクリーンショット 2017-11-28 3.12.36.png")

## React.jsについて勉強
[React.jsについて勉強](https://qiita.com/taigamikami/items/9d91aeae119faf4f4abe)
## 参考
https://mae.chab.in/archives/2943
[作って学ぶReact入門](https://www.amazon.co.jp/%E4%BD%9C%E3%82%8A%E3%81%AA%E3%81%8C%E3%82%89%E5%AD%A6%E3%81%B6-React%E5%85%A5%E9%96%80-%E5%90%89%E7%94%B0%E8%A3%95%E7%BE%8E/dp/479805075X/ref=sr_1_1?ie=UTF8&qid=1511806475&sr=8-1&keywords=React)