エンジニアリング

Railsで構築しているWebサービスをjQueryベースからReactに移行する時の知見

2016.3.2

こんにちは、エンジニアの小林です。

先日、スペースを貸し出すオーナー様向けのダッシュボード(管理画面)をリニューアルしました。
スペースマーケットはwebサーバもAPIサーバもRailsで構築しているのですが、JQueryをベースに構築していたリニューアル前の実装からReactをベースにした実装へ移行した際に得た知見を書きたいと思います。

サーバ構成

既存のサーバ構成では、webサイトはwebサーバから、アプリはAPIサーバからそれぞれデータベースを参照していました。
React-blog

リニューアルに伴いwebサーバからもAPIサーバを参照する構成となります。

webサーバから別ドメインのAPIサーバにアクセスするためには

CORSの設定

webサーバとAPIサーバはドメインが違うため、ReactのコードからAPIサーバにajaxリクエストが送れません。これを回避するためにCORS(Cross-Origin Resource Sharing)の設定を行います。

スペースマーケットのAPIサーバはRailsで動いており、CORSのサーバ設定を簡単に導入できるRackミドルウェアのrack-corsを導入しました。

設定は config/application.rb に下記のコードを追加するだけで完了です。

    config.middleware.insert_before 0, "Rack::Cors", logger: (-> { Rails.logger }) do
      allow do
        origins 'https://spacemarket.com'
        resource '*',
          headers: :any,
          methods: [:get, :post, :delete, :put, :patch, :options],
      end
    end

設定内容を簡単に解説すると、

origins
アクセス元のドメインをoriginsに指定します。
今回のケースではwebサイトからのアクセスなのでwebサイトのドメインを指定します。

resource
アクセス可能なリソースを細かく定義できます。今回は全てアクセス可能とします。

methods
アクセス可能なメソッドにはリニューアルしたアプリケーションで利用するHTTPメソッドとpreflightリクエスト用の:optionsを指定します。
これでブラウザから別ドメインであるAPIサーバにajaxリクエストが送信できるようになります。

フロントエンドでReactを使うためには

webサーバとして動いているrailsアプリケーションに以下の変更を加えてReactが動作するようにしました。

新規でgemを追加

react-rails

Reactそのものを利用するためにReactコミュニティが公式にサポートしているreact-railsを使います。
bundleした後にapp/assets/javascripts/application.jsなどでreact, react_ujsを読み込むだけで使えるようになります。

//= require react
//= require react_ujs

react_componentヘルパーを呼び出すだけで簡単にRailsからreactコンポーネントを読み込んでレンダリングすることが可能です。
第2引数にPropsをhashとして渡す事が可能で、第3引数のhashでprerender:trueを指定するとサーバーサイドレンダリングもお手軽に利用する事ができます。

<!-- app/views/owners/dashboard/spaces/edit.html.erb -->
<%= react_component('SMOSpaceLEditView', {space: @space}, {prerender: true}) %>
# app/assets/javascripts/application/views/owners/spaces/SMOSpaeEditView.js.jsx
var SMOSpaceEditView = React.createClass({
  render: function() {
    return (
      <h1>{this.props.space.name}</h1>
    );
  },
});

js-routes

SPAを実現するためにRailsのresource routeをjavascriptの世界でも使う必要があります。今回はjs-routesを採用してRailsのroutingで定義したURLをjavascript側でも使えるようにしました。

routes.rbで定義したroutingに応じてjavascript側でも同じメソッドが使えるようになります。

# config/routes.rb
resources :owners do
  namespace :dashboard , module: 'owners/dashboard' do
    resources :spaces, only: [:index, :show, :new, :edit] # owner_dashboard_space_pathが使えるようになる
  end
end
// 上記のowner_dashboard_space_pathと同じ結果を返してくれる
<a href={Routes.owner_dashboard_space_path(this.state.owner.username, this.state.space.username)}>公開して終了</a>

browserify-rails

今回新規にnpmで提供されているライブラリをいくつか追加する必要がありました。React導入とは関係ない話ですがライブラリの管理方法も今回見直してbrowserify-railsを導入することにしました。
browserify-railsを導入するとnpmのモジュールとgemとして提供されているライブラリの両方がsprocketsに載せてasset precompileできるようになります。これといった決定的な管理方法はまだ模索中ですが、現状ではこの管理方法で十分に事足りました。

npmモジュールを追加する時はpackage.jsonを更新してnpm installするだけです。

# package.json
  "dependencies": {
    "browserify": "~> 10.2.4",
    "browserify-incremental": "^3.0.1",
    "sprintf-js": "^1.0.3" // <= 追加
  }

$ npm install

capistranoでdeployしている時の注意点

スペースマーケットではcapistranoでdeployを行っているのですが、deploy時にnpm installが実行されるように設定しておく必要があります。
capistrano-npmを利用すれば簡単にdeployフックにコマンドを追加できてお手軽です。

# config/deploy/production.rb
after 'deploy:published' do
  invoke 'npm:install'
end

Reactのファイルレイアウト設計

CoCでレイアウトが決まっているRailsなどのフレームワークと違い、Reactではレイアウトを自分達で決める必要があります。
スペースマーケットでは以下のようなレイアウトを採用しました。

app/assets/javascripts/application
├── views
├── framework
├── resources
├── plugins
├── helpers
├── i18n
└── views

viewsディレクトリ

Reactコンポーネントを配置します。基本的にはURI毎にコンポーネントを定義し、再利用可能なコンポーネントは_sharedディレクトリに配置しています。
こうしておくことで、はじめてReactを触るrailsエンジニアでもrailsと同じ間隔でURLを見て確認すべきファイルを見つけやすいというメリットがあります。

views/owners/spaces/SMOSpaceEditView.js.jsx => https://spacemarket.com/owners/spacemarket/dashboard/spaces/edit
views/owners/spaces/SMOSpaceListView.js.jsx => https://spacemarket.com/owners/spacemarket/dashboard/spaces
views/_shared/form/SMFormSelectBox.js.jsx

親コンポーネントから_sharedのコンポーネントを再利用するというのが基本のコードです。

// views/owners/spaces/SMOSpaceEditView.js.jsx 
var SMOSpaceEditView = React.createClass({
  render: function() {
    <div>
      <SMFormSelectBox
        items={items}
        options={ {className:'w140'} } 
       />
    </div>
  },
});

frameworkディレクトリ

REST APIとの接続のベース部分やローカルストレージの管理等アプリケーションの基盤となるようなコードを配置します。
もっとも安定すべきコアとなる部分でありCTOがコンセプト段階で爆速でプロトタイピングしてコードを固めていきました。

resourcesディレクトリ

スペースやユーザーなどのAPIをactiverecordパターンで抽象化したクラスを配置しています。
Railsのactiverecordを使う感覚で非同期にHTTPリクエストを投げてデータを取得/更新することができます。

// app/assets/javascripts/resources/SMSpaceResource.es.jsx
"use strict"

class SMSpaceResource {
  static getSpace(id, completion){
    // スペース情報を取得する
  }

  static postSpace(params, completion){
    // スペースを作成する
  }
}

// アプリケーションのコードからスペース情報を取得する例
SMSpaceResource.getSpace(value, function(data, error, xhr){
  this.setState({
    space: data,
  });
}.bind(this);

pluginsディレクトリ

共通で利用できるローディング処理など、自前のJQuery pluginなどのコードを置いています。
基本的なDOMの更新は全てReactのstateで管理していますが、JQueryが得意とするアニメーションなどの処理は、まだまだJQueryが現役なのかなと思うところです。

// ローディングを表示するなど
$(this.refs.form).startLoading();

helperディレクトリ

Railsのapp/helpersに置くようなviewヘルパーのようなものを置いて各アプリケーションの重複するコードをDRYに保てるようにしています。
たとえばルームという会場が複数持つデータの名前は未入力なら会場の名前を表示するといったちょっとしたコードを記述しています。

// app/assets/javascripts/application/helpers/SMFormatHelper.es.jsx
"use strict"
class SMFormatHelper {
  static roomName(room){
    if (!room.name) {
      return room.space.name;
    }
    return room.name; 
  }
}

i18nディレクトリ

ローカライズする辞書の管理はもちろんRails側にあり、辞書データをjsでも使えるようにi18n-jsを利用しています。
辞書データのoutput先をこのディレクトリに設定します。

# config/i18n-js.yml
translations:
  - file: "app/assets/javascripts/i18n/translation.js"
    only: ['*.mobile', '*.dashboard','*.datetime']

その他

React Developer Tool

Chrome拡張のReact Developer Toolはコンポーネントのstateやpropsが今現在どうなっているかが簡単に確認できるので重宝しています。個人的にはこれがないともう開発できないと思います。(React触ってて知らない人はいないと思いますが..)
カレンダー___スペースマーケット

JSXの癖

Reactコンポーネントの記述ファイルであるJSXはrenderメソッド内にHTMLタグを定義するのですがちょっと癖があって最初にハマる事が多かったです。
– htmlのコメントタグは許容しない
– input、brタグは閉じタグに/>が必要
– class属性はclassName属性で定義する
– for属性はhtmlFor属性で定義する
– inlineスタイルはjavascriptのオブジェクトを渡さなければならない

このあたりは正規表現などで一括変換できるようなものを用意しておくとストレスが少ないかもしれません。
詳細は公式ドキュメントを参照してみてください。

情報収集

公式ドキュメントが非常にわかりやすくまとまっていて、かつ量が少ないので一度は全文を読む事をオススメします。
日本語の情報だと少し古いですが一人React.js Advent Calendar 2014がとても参考になりました。英語がそこまで得意ではない人はこちらで感じを掴んでもらったら後は公式を読み漁るというのが良いアプローチなのかなと思います。
最新のReact動向を追うために、ブログも購読しておくとなおいいかと思います。

まとめ

今日は既存のJQueryベースのRailsアプリケーションをReactに移行する時のノウハウについて書きました。
ファイルレイアウトはこれといった正解はないと思うので、それぞれ自分達の開発チーム、プロジェクトにあった構造を考えると良いと思います。スタートアップで仕事をしていると後で作りなおす時間はほとんど取れないので、最初にしっかり設計をしておく事をオススメします。

webサービスとAPIサーバが別ドメインにある状況で、Reactをこれからproduction環境に投入しようと思ってるエンジニアの方の参考になればと思います。

Software Engineer

Webサービス開発エンジニア。プライベートではGitHubのATOMプロジェクトにコミットしてます。ココイチが好き。

この記事について

2016.3.2

エンジニアリング

/