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