LoginSignup
9
12

More than 5 years have passed since last update.

React.jsのTutorialを試してみる(3

Posted at

前回に続いてReact.jsのTutorialを試していきます。思ってたより、長い。

Adding new comments

ようやく投稿フォームの登場です。名前とコメントを入力して送信する仕様みたいです。
CommentFormを変更します。

      var CommentForm = React.createClass({
        render: function() {
          return (
            <form className="commentForm">
              <input type="text" placeholder="Your name" />
              <input type="text" placeholder="Say something..." />
              <input type="submit" value="Post" />
            </form>
          );
        }
      });

画面表示はこんな感じです。
image
さらに仕様の追加です。送信したらメッセージをクリアしてリストを更新します。こっから細かいので変更点がわかりづらいと思います。ソースは最後にまとめて載せますが、原文のソースを追いながらのほうがわかりやすいと思います。

まずはメッセージのクリアからです。

      var CommentForm = React.createClass({
        handleSubmit: function(e) {
          e.preventDefault();
          var author = React.findDOMNode(this.refs.author).value.trim();
          var text = React.findDOMNode(this.refs.text).value.trim();
          if (!text || !author) {
            return;
          }
          // TODO: send request to the server
          React.findDOMNode(this.refs.author).value = '';
          React.findDOMNode(this.refs.text).value = '';
          return;
        },
        render: function() {
          return (
            <form className="commentForm" onSubmit={this.handleSubmit}>
              <input type="text" placeholder="Your name" ref="author" />
              <input type="text" placeholder="Say something..." ref="text" />
              <input type="submit" value="Post" />
            </form>
          );
        }
      });

ここで重要なのは3点

  • onSubmitのようにcamelCaseのハンドラが用意される
  • ref属性で指定した名前でrefsからcomponentを参照できる
  • React.findDOMNode(component)でNative DOMにアクセスできる

以上をもとにonSubmitハンドラをトリガーにして、refsとReact.findDOMNodeからinputのDOM elementを取得し、値をクリアします。

つづいてリストの更新ですが、CommentListとCommentFormは兄弟関係にあり親を経由しないとイベントを通知できません。そこでFormにonCommentSubmitハンドラを用意し、BoxはそれをトリガーにListを更新します。
まずBox側でFormにハンドラを作成します。

      var CommentBox = React.createClass({
        ...
        handleCommentSubmit: function(comment) {
          // TODO: submit to the server and refresh the list
        },
        ...
        render: function() {
          return (
            <div className="commentBox">
              ...
              <CommentForm onCommentSubmit={this.handleCommentSubmit} />
            </div>
          );
        }
      });

つぎにForm側でsubmit時にハンドラを呼び出します。

      var CommentForm = React.createClass({
        handleSubmit: function(e) {
         ...
         this.props.onCommentSubmit({author: author, text: text});
         ...
      });

つぎにBoxのhandleCommentSubmitに送信処理をajaxで実装します。しかし受信側のURLが現状静的ファイルのため、送信しても見た目上変化はありません。

  handleCommentSubmit: function(comment) {
    $.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)
    });
  },

Optimization: optimistic updates

ここで最初の目標にあげていたUX向上のための処理:サーバからの返答を待たずにリストに反映を追加します。一瞬反映されると思いますが、その後、静的ファイルがレスポンスで帰ってくるので元に戻ると思います。

        handleCommentSubmit: function(comment) {
          var comments = this.state.data;
          var newComments = comments.concat([comment]);
          this.setState({data: newComments});

Congrats!

チュートリアルとしてはここで終わりです。やっぱりちゃんと動かしたいので、用意されたソースを使ってサーバサイドも動かしたいと思います。下記zipを解凍し、npm install してください。
https://github.com/reactjs/react-tutorial/archive/master.zip

:~/workspace/react/react-tutorial-master (master) $ npm install
body-parser@1.12.2 node_modules/body-parser
├── content-type@1.0.1
├── bytes@1.0.0
├── raw-body@1.3.3
├── depd@1.0.0
├── qs@2.4.1
├── iconv-lite@0.4.7
├── on-finished@2.2.0 (ee-first@1.1.0)
├── debug@2.1.3 (ms@0.7.0)
└── type-is@1.6.1 (media-typer@0.3.0, mime-types@2.0.10)

express@4.12.3 node_modules/express
├── merge-descriptors@1.0.0
├── utils-merge@1.0.0
├── methods@1.1.1
├── cookie@0.1.2
├── fresh@0.2.4
├── cookie-signature@1.0.6
├── escape-html@1.0.1
├── range-parser@1.0.2
├── content-type@1.0.1
├── finalhandler@0.3.4
├── vary@1.0.0
├── parseurl@1.3.0
├── serve-static@1.9.2
├── content-disposition@0.5.0
├── path-to-regexp@0.1.3
├── depd@1.0.0
├── on-finished@2.2.0 (ee-first@1.1.0)
├── qs@2.4.1
├── debug@2.1.3 (ms@0.7.0)
├── etag@1.5.1 (crc@3.2.1)
├── send@0.12.2 (destroy@1.0.3, ms@0.7.0, mime@1.3.4)
├── proxy-addr@1.0.7 (forwarded@0.1.0, ipaddr.js@0.1.9)
├── type-is@1.6.1 (media-typer@0.3.0, mime-types@2.0.10)
└── accepts@1.2.5 (negotiator@0.5.1, mime-types@2.0.10)

入りました。cloud9上で動かす場合
server.jsの42行目を以下のように変更してください。

app.listen(process.env.PORT, process.env.IP);

その後、public/index.htmlを作成したものに置き換えましょう。置き換えなくても動きますけど、置き換えましょう。
最後にnpm startでサーバを起動させ、ファイルにアクセスします。ブラウザを複数開けば同期しているのがわかると思います。

サーバサイドの処理を詳しく知りたい方はserver.jsをみるといいと思います。
40行くらいで、ファイルに読み書きしているようです。

最終的なindex.htmlを最後に載せておきます。

<!-- index.html -->
<html>
  <head>
    <title>Hello React</title>
    <script src="https://fb.me/react-0.13.1.js"></script>
    <script src="https://fb.me/JSXTransformer-0.13.1.js"></script>
    <script src="https://code.jquery.com/jquery-1.10.0.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/0.3.1/showdown.min.js"></script>
  </head>
  <body>
    <div id="content"></div>
    <script type="text/jsx">
      var data = [
        {author: "Pete Hunt", text: "This is one comment"},
        {author: "Jordan Walke", text: "This is *another* comment"}
      ];
      var converter = new Showdown.converter();
      var Comment = React.createClass({
        render: function() {
          var rawMarkup = converter.makeHtml(this.props.children.toString());
          return (
            <div className="comment">
              <h2 className="commentAuthor">
                {this.props.author}
              </h2>
              <span dangerouslySetInnerHTML={{__html: rawMarkup}} />
            </div>
          );
        }
      });

      var CommentList = React.createClass({
        render: function() {
          var commentNodes = 
                this.props.data.map(function (comment) {
            return (
              <Comment author={comment.author}>
                {comment.text}
              </Comment>
            );
          });
          return (
            <div className="commentList">
              {commentNodes}
            </div>
          );
        }
      });

      var CommentForm = React.createClass({
        handleSubmit: function(e) {
          e.preventDefault();
          var author = React.findDOMNode(this.refs.author).value.trim();
          var text = React.findDOMNode(this.refs.text).value.trim();
          if (!text || !author) {
            return;
          }
          this.props.onCommentSubmit({author: author, text: text});
          React.findDOMNode(this.refs.author).value = '';
          React.findDOMNode(this.refs.text).value = '';
          return;
        },
        render: function() {
          return (
            <form className="commentForm" onSubmit={this.handleSubmit}>
              <input type="text" placeholder="Your name" ref="author" />
              <input type="text" placeholder="Say something..." ref="text" />
              <input type="submit" value="Post" />
            </form>
          );
        }
      });

      var CommentBox = React.createClass({
        loadCommentsFromServer: function() {
          $.ajax({
            url: this.props.url,
            dataType: 'json',
            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) {
          var comments = this.state.data;
          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) {
              console.error(this.props.url, status, err.toString());
            }.bind(this)
          });
        },
        getInitialState: function() {
          return {data: []};
        },
        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 onCommentSubmit={this.handleCommentSubmit} />
            </div>
          );
        }
      });

      React.render(
        <CommentBox url="comments.json" pollInterval={2000} />,
        document.getElementById('content')
      );
    </script>
  </body>
</html>
9
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
9
12