アナログ金木犀

つれづれなるまままにつれづれする

React.jsを触ってみた

本家にあるTutorialをさわってみたのでメモ。

serverを動かす

https://github.com/reactjs/react-tutorial/

ここにserverのスクリプトが置いてある。download

POSTメソッドを動かす必要性もあるので、serverを動かす必要があるのですが、serverを起動するスクリプトも入っている。いつもRuby使ってるので、僕はRubyで。

ruby server.rb

http://localhost:3000/ で動いているかどうか確認できる。

index.htmlを準備する

public/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>
  </head>
  <body>
    <div id="content"></div>
    <script type="text/jsx">
      // Your code here
    </script>
  </body>
</html>

Componentを作ってみる。

JSXな書き方とplainJSな書き方があるらしい。

// JSXな書き方
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        Hello, world! I am a CommentBox.
      </div>
    );
  }
});
React.render(
  <CommentBox />,
  document.getElementById('content')
);
// plainJSな書き方
var CommentBox = React.createClass({displayName: 'CommentBox',
  render: function() {
    return (
      React.createElement('div', {className: "commentBox"},
        "Hello, world! I am a CommentBox."
      )
    );
  }
});
React.render(
  React.createElement(CommentBox, null),
  document.getElementById('content')
);

JSX in Depthを見る限り「JSXを使わなくてもいいけど、オススメしてるよ」って言ってるので、JSXなスタイルでいこうと思う。

ソースを見る限り、createClassでComponentを作って、JSX部分ではHTMLを作る処理を書き、React.renderで描画するものと場所を指定しているっぽい。

ということは下記だけでも同様な動きになりそう。

    React.render(
      <div className="commentBox">
        Hello, world! I am a CommentBox.
      </div>
      ,
      document.getElementById('content')
    );

同様な動きでした。

Componentをネストしてみる。

commentBoxというコンポーネントにh1タグ、commentListのコンポーネント、commentFormのコンポーネントをネストしたもの(下記)を作ってみる。

  <div class="commentBox">
    <h1>Comments</h1>
    <div class="commentList">
      Hello, world! I am a CommentList.
    </div>
    <div class="commentForm">
      Hello, world! I am a CommentForm.
    </div>
  </div>

実際に組んでみると下記のようになる。

    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>
        );
      }
    });
    var CommentBox = React.createClass({
      render: function() {
        return (
          <div className="commentBox">
            <h1>Comments</h1>
            <CommentList />
            <CommentForm />
          </div>
        );
      }
    });
    React.render(
      <CommentBox />,
      document.getElementById('content')
    );

ちなみにJSXを使わない場合は下記。

    var CommentList = React.createClass({displayName: 'CommentList',
      render: function() {
        return (
          React.createElement('div', {className: 'commentList'},
            "Hello, world! I am a CommentList."
          )
        );
      }
    });

    var CommentForm = React.createClass({displayName: 'CommentForm',
      render: function() {
        return (
          React.createElement('div', {className: 'commentForm'},
            "Hello, world! I am a CommentForm."
          )
        );
      }
    });

    var CommentBox = React.createClass({displayName: 'CommentBox',
      render: function() {
        return (
          React.createElement('div', {className: "commentBox"},
            React.createElement('h1', null , "Comments"),
            React.createElement(CommentList, null),
            React.createElement(CommentForm, null)
          )
        );
      }
    });
    React.render(
      React.createElement(CommentBox, null),
      document.getElementById('content')
    );

createElementの第3引数以降にelementのインスタンスを入れるとネストされ、並列に置きたいときは第4引数、第5引数としていけばいいらしい。

この点に関しては

          <div className="commentBox">
            <h1>Comments</h1>
            <CommentList />
            <CommentForm />
          </div>

と書く事ができるJSXの方が確かに全然見やすい。

Propsを使ってみる

Propsは親コンポーネントから子コンポーネントへのデータを渡す方法。 とりあえずCommentコンポーネントを作成する。

var Comment = React.createClass({
  render: function() {
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        {this.props.children}
      </div>
    );
  }
});

コンポーネントから子コンポーネントpropsを通してデータを渡す事ができる。 親コンポーネントのCommentListを下記のように記述する事でCommentコンポーネントprops.authorの情報を読み取る事ができる。

var CommentList = React.createClass({
  render: function() {
    return (
      <div className="commentList">
        <Comment author="Pete Hunt">This is one comment</Comment>
        <Comment author="Jordan Walke">This is *another* comment</Comment>
      </div>
    );
  }
});

またCommentは、ネストされた中身(上の例だとThis is one commentThis is *another* commentの部分)を this.props.children から取得する事ができる。

jsonデータをCommentListに表示してみる。

まずはデータを用意。

var data = [
  {author: "Pete Hunt", text: "This is one comment"},
  {author: "Jordan Walke", text: "This is *another* comment"}
];

データをCommentBox -> CommentListという感じで渡していく。

var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.props.data} />
        <CommentForm />
      </div>
    );
  }
});

React.render(
  <CommentBox data={data} />,
  document.getElementById('content')
);

CommentListでdataを使って表示。

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

dataという配列をmap関数で各要素のhtmlを作ってそれを繋げて、{commentNodes}で表示している。

dataをサーバーから取ってくる。

"http://localhost:3000/comments.js" からコメントを取得して、表示してみる。

ソースはこうなる。

    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)
        });
      },
      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 />
          </div>
        );
      }
    });

    React.render(
      <CommentBox url="comments.json" pollInterval={2000} />,
      document.getElementById('content')
    );

getInitialStateはコンポーネントが作成されて一番始めに呼ばれ、componentDidMountはrender処理が走ったときに呼ばれる。

ここでは getIntialStateで空のdataプロパティを作成し、render時にAPIを走らせてデータを取得したときにsetStateでdataにAPIの結果を詰めている。 またsetIntaervalで2秒毎にAPIを読んでアップデートしている。

setStateはpropsを変更するときに使うメソッドみたい。

コメントを追加してみる。

formを作ってそこからコメントを追加していく。

プログラムの流れとしては

CommentFormでsubmitすると、空チェックして、CommentBoxに値を投げる。そして、成功したら表示、失敗したらそれなりの処理をする。

という感じ。

CommentFormはこういう感じになる。

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

submitされた場合に、handleSubmitが呼ばれるようにして、値は“refs“を使って取得。 下記のように、props.onCommentSubmitにCommentBoxへのCallbackを登録しておくことで CommentForm -> CommentBoxというデータの流れを作る事ができる。

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) {
    $.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>
    );
  }
});

一部飛ばしてるけど、以上でチュートリアルは終了。