LoginSignup
14
18

More than 5 years have passed since last update.

React Tutorial Example (Hot Module Replacement)

Last updated at Posted at 2016-06-09

React tutorialReact の基本を学ぶのに良いテキストです。
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 parserremarkable を使います。
  • AjaxJQuery を使います。
$ npm install remarkable jquery --save-dev

Add babelrc

  • Babel preset に React plugin を設定します。
.babelrc
{
  "presets": ["react"]
}

Add webpack config

  • Webpack に HMR の設定をします。
    • outputbundle.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

$ 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

  • ReactReactDOM のモジュールのロードを設定します。
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

express

React Developer Toolsを利用すると要素の確認など便利な機能が使えます。

Start React Tutorial

ここからが React tutorial です。チュートリアルの順番にコードの差分を表示しています。
(HMR を利用するために index.jsapp.js のファイルにコードを分離しています。)

Tutorial 1

  • React.createClassCommentBox コンポーネントを作成します。
  • 作成したコンポーネントを 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')
+);

express

Tutorial 2

  • CommentListCommentForm のコンポーネントを作成します。
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 コンポーネントに CommentListCommentForm のコンポーネントを追加します。
app.js
 var CommentBox = React.createClass({
   render: function() {
     return (
       <div className="commentBox">
-        Hello, world! I am a CommentBox.
+        <h1>Comments</h1>
+        <CommentList />
+        <CommentForm />
       </div>
     );
   }

express

Tutorial 4

  • Comment コンポーネントを作成します。
    • this.props.authorthis.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 コンポーネントには authorchild 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>
     );
   }

express

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

express

Tutorial 7

  • dangerouslySetInnerHTMLMarkdown のレンダリングを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>
     );
   }

express

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

express

Tutorial 13

  • Ajax で取得されたデータを this.setStatedata に値を設定します。
    • 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>
     );
   }
 });

express

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 コンポーネントの handleCommentSubmitcomment を追加する 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 に値を設定します。(idComment コンポーネントの 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)
     });
   },

express

Congrats!


構成部品を CommentBox, CommentList, Comment, CommentForm に分離できました。
次は React tutorial に Redux を追加 しましょう!

14
18
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
18