LoginSignup
14
12

More than 3 years have passed since last update.

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

Last updated at Posted at 2017-11-27

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入門

14
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
12