Help us understand the problem. What is going on with this article?

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

React Tutorial

Reactの公式チュートリアルを日本語訳してくださったありがたいサイトがある(ReactTutorial日本語訳)

公式TutorialではCodePenのコードを利用して進めろとあるが、Componentが複数定義され、長くて見づらくなってしまうと思い、Componentを別ファイルに分けて行った。
環境自体もCodePenを用いていないので自身で環境構築を行った。
最終GOAL(CodePen)

環境構築

アプリケーション用ディレクトリ作成

$mkdir react-tutorial
$cd react-tutorial
$npm init -y

npmパッケージのインストール

$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)

mkdir src public

設定ファイルの作成

.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

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

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

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

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

実行方法

$npm start

実行結果

スクリーンショット 2017-11-28 3.12.36.png

React.jsについて勉強

React.jsについて勉強

参考

https://mae.chab.in/archives/2943
作って学ぶReact入門

taigamikami
大学生 自分の勉強・メモとしてQiitaに投稿しています。おかしいと思う部分は遠慮なくご指摘いただければと思います。 Ruby/Rails/Swift/iOS/Python
https://taigamikami.github.io/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした