yikegaya’s blog

仕事関連(Webエンジニア)と資産運用について書いてます

Rails APIモード + Reactに入門してみる

Reactのチュートリアルをやったので、次にRailsと連携させてみた。

RailsとReactの連携方法を調べるとざっくり2パターンあって

  1. Rails側でルータやビューを作ってjQueryの代わりのライブラリとしてReactを導入する。
  2. APIモードでRailsアプリを作って、それとは違うポート番号でReactのアプリを起動してRailsから飛ばした APIとReactを連動させる

前者はすでにRailsで作られたアプリがあってとりあえずそこでReactを試してみたい。という場合に採用される。

メジャーなやり方は後者で、メリットもこっちのが大きくて以下が挙げられる

  • フロントエンドとバックエンドの開発を切り分けれられる。デプロイも別々にできたりする
  • ルータをReactに寄せられるのでSPAで快適なユーザ体験を作れる

いい感じのチュートリアルがないか探してみた

なんかいい解説ないかなと探してみて以下のQiitaの記事が良さそうだったので参考にさせてもらい作ってみることにした。

react.orgのReactチュートリアルのアプリとRailsを連携させる解説記事。

qiita.com

この記事が書かれたのは2017年で今はReactチュートリアルの内容は変わっている(この記事のアプリはテキストフォームから文字列を入力して入力したコメント一覧を画面に表示するアプリを作るのが、2019年現在はReactチュートリアルの内容が三目並べに変わっている)

が、2017年時点でのReactチュートリアルのコードは Githubに置いてある ので特に問題なくできる。

仕組み

react側の実装

public以下にjavascript(example.js)を置いて、あらかじめreactチュートリアルで用意されているrubyで書かれたwebサーバ(server.rb)を起動する。

server.rbに以下のようにHTTPリクエストの内容とURLを指定している。

  if req.request_method == 'POST'
    # Assume it's well formed
    comment = { id: (Time.now.to_f * 1000).to_i }
    req.query.each do |key, value|
      comment[key] = value.force_encoding('UTF-8') unless key == 'id'
    end
    comments << comment
    File.write(
      './comments.json',
      JSON.pretty_generate(comments, indent: '    '),
      encoding: 'UTF-8'
    )
  end

example.jsを読むと最終的にHTMLを描画するrenderメソッドの中身は以下のようになっていて

  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm onCommentSubmit={this.handleCommentSubmit} />
      </div>
    );
  }

コメントリストはコンポーネントでstateに入れて、コメント送信はhandleCommentSubmitメソッドで実装されている。

コメント登録処理

handleCommentSubmitメソッドの中をみてみると、引数にフォーム入力したcommentを取ってそれをAjaxjson形式でPOSTしている。

引数に取ったcommentは他のコメントとconcatしてreactのsetStateで状態保持させる。

  handleCommentSubmit: function(comment) {
    var comments = this.state.data;
    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)
    });
  },

飛ばす先のURLはReactDOM.renderで指定している

ReactDOM.render(
  <CommentBox url="http://localhost:3001/comments" pollInterval={2000} />,
  document.getElementById('content')
);
コメント表示処理

Railsがデータベースからコメントを取り出してAPIを飛ばす。それをserver.rbでJSONに入れてReactの状態に入れて描画している。

フォーム送信時(POSTした時)は追加されたコメントがReact側の状態(props)に追加されてそれも描画される。

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">
        {commentNodes}
      </div>
    );
  }
});

rails側の実装

まず/comments へのpostを受けとるとコントローラのcreateメソッドを呼ぶようにルータを定義。

createメソッドの中身は以下の通りでパラメータをCommentモデルに渡してsaveする。 最初に全コメントを描画するためのselect(Comment.all)もこのメソッド内でやってしまう。

  def create
    @comment = Comment.new(author: params[:author], text: params[:text])

    if @comment.save
      comments = Comment.all
      render json: comments, status: :created, location: @comment
    else
      render json: @comment.errors, status: :unprocessable_entity
    end
  end

やってみて

やっぱ読むだけより実際手を動かして動くところまで作ると気づきが多い感ある。

ただ今回のやり方はReactをrubyで書いたwebサーバで起動してそのサーバのコード内でHTTPリクエストの内容とURLを指定して実装していたけどそのやり方はメジャーではないかもしれない。

Reactはyarnとかnpmで起動させてAPIの受け渡しはjavascript側でaxiosとか使ってやってるところが多いのではないかと。その辺のやり方は別途深掘りしたいけど入門としては今回くらいがわかりやすくてよかった。