React tutorial は React の基本を学ぶのに良いテキストです。
React は HMR(Hot Module Replacement) がとても便利なので、
ブラウザのリロードの負担を軽減したチュートリアルの進め方を紹介します。
Learning
- React tutorial を Hot Module Replacement を利用する。
- Web server に webpack-dev-server を利用する。
Environment
- node: v4.4.5
- npm: v3.9.6
Comment Box Form
- 完成される Source Code のファイルリストです。
$ tree -a -I node_modules
.
├── .babelrc
├── app.js
├── index.html
├── index.js
├── package.json
├── style.css
└── webpack.config.js
Let's hands-on
Initial application
-
npm init
コマンドでpackage.json
ファイルを作成します。
$ mkdir react-comment-box-example && cd react-comment-box-example
$ npm init --force
Add package
- React で HMR を実行するモジュールをインストールします。
$ npm install webpack webpack-dev-server --save-dev
$ npm install babel-core babel-loader babel-preset-react --save-dev
$ npm install react react-hot-loader react-dom --save-dev
- Markdown parser は remarkable を使います。
- Ajax は JQuery を使います。
$ npm install remarkable jquery --save-dev
Add babelrc
- Babel preset に React plugin を設定します。
.babelrc
{
"presets": ["react"]
}
Add webpack config
- Webpack に HMR の設定をします。
-
output
のbundle.js
は物理のファイルは出力されません。
-
webpack.config.js
var webpack = require('webpack')
module.exports = {
entry: './index',
output: {
filename: 'bundle.js'
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loaders: [
'react-hot',
'babel'
]
}
]
},
devServer: {
hot: true,
port: 4000,
inline: true,
historyApiFallback: true
}
}
Add npm start
-
npm start
コマンドでwebpack-dev-server
を実行する設定をstart
にします。
package.json
"scripts": {
+ "start": "webpack-dev-server -d --progress --colors",
"test": "echo \"Error: no test specified\" && exit 1"
},
Add Stylesheet
- Stylesheet を React Tutorial からダウンロードします。
$ curl https://raw.githubusercontent.com/reactjs/react-tutorial/master/public/css/base.css \
-o style.css
Add View
- HTML に DOM をレンダリングする要素の
<div id='content'>
を設定します。 -
<script src='/bundle.js'>
は Webpack が出力するスクリプトです。
index.html
<!DOCTYPE html>
<html>
<head>
<title>Comment Box Example</title>
<link rel='stylesheet' href='/style.css' />
</head>
<body>
<div id='content'></div>
<script src='/bundle.js'></script>
</body>
</html>
Add React
-
React
とReactDOM
のモジュールのロードを設定します。
index.js
var React = require('react');
var ReactDOM = require('react-dom');
Start HTTP Server
-
npm start
コマンドで Webアプリケーション を実行します。 - ブラウザで http://localhost:4000 をロードすると Comment Box Example が表示されます。
$ npm start
$ open http://localhost:4000
React Developer Toolsを利用すると要素の確認など便利な機能が使えます。
Start React Tutorial
ここからが React tutorial です。チュートリアルの順番にコードの差分を表示しています。
(HMR を利用するために index.js
と app.js
のファイルにコードを分離しています。)
Tutorial 1
-
React.createClass
で CommentBox コンポーネントを作成します。 - 作成したコンポーネントを
ReactDOM.render
でレンダリングします。
app.js
+var React = require('react');
+var Remarkable = require('remarkable');
+var $ = require('jquery');
+
+var CommentBox = React.createClass({
+ render: function() {
+ return (
+ <div className="commentBox">
+ Hello, world! I am a CommentBox.
+ </div>
+ );
+ }
+});
+
+module.exports = CommentBox;
index.js
+var CommentBox = require('./app');
+
+ReactDOM.render(
+ <CommentBox />,
+ document.getElementById('content')
+);
Tutorial 2
- CommentList と CommentForm のコンポーネントを作成します。
app.js
+var CommentList = React.createClass({
+ render: function() {
+ return (
+ <div className="commentList">
+ Hello, world! I am a CommentList.
+ </div>
+ );
+ }
+});
+
+var CommentForm = React.createClass({
+ render: function() {
+ return (
+ <div className="commentForm">
+ Hello, world! I am a CommentForm.
+ </div>
+ );
+ }
+});
Tutorial 3
- CommentBox コンポーネントに CommentList と CommentForm のコンポーネントを追加します。
app.js
var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
- Hello, world! I am a CommentBox.
+ <h1>Comments</h1>
+ <CommentList />
+ <CommentForm />
</div>
);
}
Tutorial 4
-
Comment コンポーネントを作成します。
-
this.props.author
とthis.props.author
はコンポーネントのプロパティがレンダリングされます。
-
app.js
+var Comment = React.createClass({
+ render: function() {
+ return (
+ <div className="comment">
+ <h2 className="commentAuthor">
+ {this.props.author}
+ </h2>
+ {this.props.children}
+ </div>
+ );
+ }
+});
Tutorial 5
- CommentList コンポーネントに Comment コンポーネントを追加します。
-
Comment コンポーネントには
author
と child Nodes のプロパティを設定します。
app.js
var CommentList = React.createClass({
render: function() {
return (
<div className="commentList">
- Hello, world! I am a CommentList.
+ <Comment author="Pete Hunt">This is one comment</Comment>
+ <Comment author="Jordan Walke">This is *another* comment</Comment>
</div>
);
}
Tutorial 6
- Comment コンポーネントに Markdown のレンダリングを追加します。
app.js
var Comment = React.createClass({
render: function() {
+ var md = new Remarkable();
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
- {this.props.children}
+ {md.render(this.props.children.toString())}
</div>
);
}
Tutorial 7
-
dangerouslySetInnerHTML
で Markdown のレンダリングをHTMLの要素に変換します。
app.js
var Comment = React.createClass({
- render: function() {
+ rawMarkup: function() {
var md = new Remarkable();
+ var rawMarkup = md.render(this.props.children.toString());
+ return { __html: rawMarkup };
+ },
+
+ render: function() {
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
- {md.render(this.props.children.toString())}
+ <span dangerouslySetInnerHTML={this.rawMarkup()} />
</div>
);
}
Tutorial 8
-
Comment コンポーネントのプロパティのデータを準備します。
- データはJSON形式の配列です。
index.js
+var data = [
+ {id: 1, author: "Pete Hunt", text: "This is one comment"},
+ {id: 2, author: "Jordan Walke", text: "This is *another* comment"}
+];
Tutorial 9
- まず CommentBox コンポーネントのプロパティに
data
を設定します。 - 次に CommentList コンポーネントのプロパティに
data
を設定します。
index.js
ReactDOM.render(
- <CommentBox />,
+ <CommentBox data={data} />,
document.getElementById('content')
);
app.js
var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
- <CommentList />
+ <CommentList data={this.props.data} />
<CommentForm />
</div>
);
}
});
Tutorial 10
- 配列のデータを Comment コンポーネントのプロパティに設定します。
-
map()
でcommentNodes
の変数にセットされます。
-
app.js
var CommentList = React.createClass({
render: function() {
+ var commentNodes = this.props.data.map(function(comment) {
+ return (
+ <Comment author={comment.author} key={comment.id}>
+ {comment.text}
+ </Comment>
+ );
+ });
return (
<div className="commentList">
- <Comment author="Pete Hunt">This is one comment</Comment>
- <Comment author="Jordan Walke">This is *another* comment</Comment>
+ {commentNodes}
</div>
);
}
Tutorial 11
- データを API で取得するために
url
プロパティにエンドポイントを設定します。
index.js
ReactDOM.render(
- <CommentBox data={data} />,
+ <CommentBox url="http://localhost:3000/api/comments" />,
document.getElementById('content')
);
(API は React Tutorial Example (Express) をご利用ください。)
Tutorial 12
-
data
をプロパティではなくコンポーネントで値を設定するためにthis.state.data
に置き換えます。-
getInitialState()
でdata
を初期化します。
-
app.js
var CommentBox = React.createClass({
+ getInitialState: function() {
+ return {data: []};
+ },
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
- <CommentList data={this.props.data} />
+ <CommentList data={this.state.data} />
<CommentForm />
</div>
);
Tutorial 13
-
Ajax で取得されたデータを
this.setState
でdata
に値を設定します。-
componentDidMount()
はコンポーネントが最初にレンダリングされたときにコールされます。
-
app.js
var CommentBox = React.createClass({
getInitialState: function() {
return {data: []};
},
+ componentDidMount: function() {
+ $.ajax({
+ url: this.props.url,
+ dataType: 'json',
+ cache: false,
+ success: function(data) {
+ this.setState({data: data});
+ }.bind(this),
+ error: function(xhr, status, err) {
+ console.error(this.props.url, status, err.toString());
+ }.bind(this)
+ });
+ },
render: function() {
return (
<div className="commentBox">
Tutorial 14
-
pollInterval
プロパティを追加してsetInterval()
で定期的にデータを取得します。
index.js
ReactDOM.render(
- <CommentBox url="http://localhost:3000/api/comments" />,
+ <CommentBox url="http://localhost:3000/api/comments" pollInterval={2000} />,
document.getElementById('content')
);
app.js
var CommentBox = React.createClass({
getInitialState: function() {
return {data: []};
},
- componentDidMount: function() {
+ loadCommentsFromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
+ componentDidMount: function() {
+ this.loadCommentsFromServer();
+ setInterval(this.loadCommentsFromServer, this.props.pollInterval);
+ },
render: function() {
return (
<div className="commentBox">
Tutorial 15
- CommentForm コンポーネントにフォームを追加します。
app.js
var CommentForm = React.createClass({
render: function() {
return (
- <div className="commentForm">
- Hello, world! I am a CommentForm.
- </div>
+ <form className="commentForm">
+ <input type="text" placeholder="Your name" />
+ <input type="text" placeholder="Say something..." />
+ <input type="submit" value="Post" />
+ </form>
);
}
});
Tutorial 16
-
CommentForm コンポーネントの
<input>
のonChange
をハンドルとアタッチします。
app.js
var CommentForm = React.createClass({
+ getInitialState: function() {
+ return {author: '', text: ''};
+ },
+ handleAuthorChange: function(e) {
+ this.setState({author: e.target.value});
+ },
+ handleTextChange: function(e) {
+ this.setState({text: e.target.value});
+ },
render: function() {
return (
<form className="commentForm">
- <input type="text" placeholder="Your name" />
- <input type="text" placeholder="Say something..." />
+ <input
+ type="text"
+ placeholder="Your name"
+ value={this.state.author}
+ onChange={this.handleAuthorChange}
+ />
+ <input
+ type="text"
+ placeholder="Say something..."
+ value={this.state.text}
+ onChange={this.handleTextChange}
+ />
<input type="submit" value="Post" />
</form>
);
Tutorial 17
-
CommentForm コンポーネントの
<form>
のonSubmit
をハンドルとアタッチします。
app.js
var CommentForm = React.createClass({
handleTextChange: function(e) {
this.setState({text: e.target.value});
},
+ handleSubmit: function(e) {
+ e.preventDefault();
+ var author = this.state.author.trim();
+ var text = this.state.text.trim();
+ if (!text || !author) {
+ return;
+ }
+ // TODO: send request to the server
+ this.setState({author: '', text: ''});
+ },
render: function() {
return (
- <form className="commentForm">
+ <form className="commentForm" onSubmit={this.handleSubmit}>
<input
type="text"
placeholder="Your name"
Tutorial 18
-
CommentForm コンポーネントの
onCommentSubmit
プロパティをハンドルとアタッチします。
app.js
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
+ handleCommentSubmit: function(comment) {
+ // TODO: submit to the server and refresh the list
+ },
componentDidMount: function() {
this.loadCommentsFromServer();
setInterval(this.loadCommentsFromServer, this.props.pollInterval);
},
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
- <CommentForm />
+ <CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>
);
}
Tutorial 19
-
CommentForm コンポーネントの
this.props.onCommentSubmit
に引数を設定します。
app.js
var CommentForm = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
var author = this.state.author.trim();
var text = this.state.text.trim();
if (!text || !author) {
return;
}
- // TODO: send request to the server
+ this.props.onCommentSubmit({author: author, text: text});
this.setState({author: '', text: ''});
},
render: function() {
Tutorial 20
-
CommentBox コンポーネントの
handleCommentSubmit
にcomment
を追加する Ajax を設定します。
app.js
var CommentBox = React.createClass({
handleCommentSubmit: function(comment) {
- // TODO: submit to the server and refresh the list
+ $.ajax({
+ url: this.props.url,
+ dataType: 'json',
+ type: 'POST',
+ data: comment,
+ success: function(data) {
+ this.setState({data: data});
+ }.bind(this),
+ error: function(xhr, status, err) {
+ console.error(this.props.url, status, err.toString());
+ }.bind(this)
+ });
},
componentDidMount: function() {
this.loadCommentsFromServer();
Tutorial 21
-
comment.id
に値を設定します。(id
は Comment コンポーネントのkey
に必要です。) -
Ajax を実行する前に
setState
を設定します。-
comment
がサーバに追加される前にcomment
を表示することができます。
-
app.js
var CommentBox = React.createClass({
handleCommentSubmit: function(comment) {
+ var comments = this.state.data;
+ // Optimistically set an id on the new comment. It will be replaced by an
+ // id generated by the server. In a production application you would likely
+ // not use Date.now() for this and would have a more robust system in place.
+ comment.id = Date.now();
+ var newComments = comments.concat([comment]);
+ this.setState({data: newComments});
$.ajax({
url: this.props.url,
dataType: 'json',
type: 'POST',
data: comment,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
+ this.setState({data: comments});
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
Congrats!
構成部品を CommentBox, CommentList, Comment, CommentForm に分離できました。
次は React tutorial に Redux を追加 しましょう!