React.jsチュートリアルをやる(3)


[React.jsチュートリアルをやる(2)](http://qiita.com/saekis/items/596aad5a239a597ed4f5)の続き。

## コメントの追加
### フォームをつくるコメント
コメントを送信するためのコメントフォームを作る

```js
var CommentForm = React.createClass({
  handleSubmit: function(e) {
    e.preventDefault();
    var author = ReactDOM.findDOMNode(this.refs.author).value.trim();
    var text = ReactDOM.findDOMNode(this.refs.text).value.trim();
    if (!text || !author) {
      return;
    }
    // TODO: サーバにリクエストを送信
    ReactDOM.findDOMNode(this.refs.author).value = '';
    ReactDOM.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>
    );
  }
});
```

#### イベント
コンポーネントにイベントを張る場合はキャメルケースで書く。上のコードではフォームに`onSubmit`イベントを登録していて、フォームがsubmitされると`handleSubmit()`が呼ばれる。

#### Refs
コンポーネントにref属性をつけることで、コンポーネントを識別できるようなる。上のコードでは`handleSubmit`の中の`this.refs.author``this.refs.text`でそれぞれのインプットボックスを取得している。
`ReactDOM.findDOMNode(component)`で実際のDOM要素を取得できる。

#### 子から親へ
コメント投稿フォームでコメントがsubmitされた場合、`CommentList`コンポーネントのデータをリフレッシュする必要がある。
コメントリストのstateは`CommentBox`で管理しているので、必要なロジックは`CommentBox`に書く。
`<CommentForm onCommentSubmit={this.handleCommentSubmit} />`で`onCommentSubmit`と`handleCommentSubmit`を結びつけることで、`onCommentSubmit`が呼ばれるたびに`handleCommentSubmit`が実行されるようになる。

```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: サーバに送信、リストをリフレッシュ
  },
  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>
    );
  }
});
```

コールバックの準備ができたので、コメントがsubmitされたら`CommentForm`からコールバックを呼べるようにする。

```js
var CommentForm = React.createClass({
  handleSubmit: function(e) {
    e.preventDefault();
    var author = ReactDOM.findDOMNode(this.refs.author).value.trim();
    var text = ReactDOM.findDOMNode(this.refs.text).value.trim();
    if (!text || !author) {
      return;
    }
    // コールバック呼び出し
    this.props.onCommentSubmit({author: author, text: text});
    ReactDOM.findDOMNode(this.refs.author).value = '';
    ReactDOM.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>
    );
  }
});
```

最後に、`CommentForm`にてsubmitされたコメントを`CommentBox`コンポーネントでにてサーバへ送信し、stateをリフレッシュするようにする。 

```js
var CommentBox = React.createClass({
  loadCommentsFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: function(data) {
        // state更新
        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>
    );
  }
});
```

#### 先読み更新

サーバからコメント送信のレスポンスが帰ってくる前にstateをリフレッシュさせ、リスト更新の体感速度をアップさせる。

```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) {
    var comments = this.state.data;
    var newComments = comments.concat([comment]);
    // コメントをサーバへ送信する前にstateを更新する
    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)
    });
  },
  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>
    );
  }
});
```

今回コメントの取得ロジックを`CommentBox`に書いたけど、役割で言えばコメント一覧を表示する`CommentList`コンポーネントに書いた方がいいのでは？と思った。

あと今回はわかりやすい例だったけど、開発ではどのレベルでコンポーネントとして切り出すかとか結構悩みそうな気がする。
コンポーネント化による恩恵とHTMLの管理のしやすさを天秤にかけていく感じかな。



## おしまい
React楽しい！