ランサーズ(Lancers)エンジニアブログ > JavaScript > ランサーズ流 React.js/redux アプリ開発入門

ランサーズ流 React.js/redux アプリ開発入門

mori-dev|2016年12月13日
JavaScript

はじめまして、森(@mori-dev) と申します。React.js/redux, Rails, Node を使ったアプリの開発を行っています。

このエントリーでは、ランサーズでの React.js/redux プロジェクトの設計/実装などの雰囲気がわかるような概要を書きます。

じっさいのプロジェクトのコードはそれなりのサイズですので、lancers-redux-sample-app という形で基本的な部分を取り出しました。GitHub で公開しています。

コミットはすべて私になっていますが、チームの成果からの抽出です。フロント側の開発メンバーは私の他に @numanomanu さんと @takepo さんです。
ではコミットを追ってゆきます。パッケージ管理では yarn を使っています。

yarn 導入以前は、npm shrinkwrap でした。その頃は ci-npm-update も使っていました。現在はパッケージのアップデートは、ときどき yarn-update で行っています。CI 連携させたいですね。
CircleCI の yarn の設定はこのようにしています。

もっと高速にできそうな資料 <a href="http://r7kamura.hatenablog.com/entry/2016/12/08/061203" target="_blank" rel="noopener">amakanでyarnを使うようにした</a> はみつけたのですが、まだ試していません。CIの遅さは Rails アプリ側の方がひどいので、対応の優先度は低いです。
ディレクトリレイアウトはこうです。普通です。

containers と components の使い分けは、いくつか記事をみかけましたが Container Components が参考になります。

プロジェクト単位での npm の設定を、プロジェクトディレクトリ直下に .npmrc を配置して行います。

npmrc の使い方は <a href="http://qiita.com/inuscript/items/86dbfd26abe6905756c0" target="_blank" rel="noopener">project毎のnpmコマンドをいい感じにするnpmrc &amp; config達</a> が参考になります。今回設定したキャレット(^)とチルダ(~)の違いは <a href="http://qiita.com/sotarok/items/4ebd4cfedab186355867" target="_blank" rel="noopener">package.json のチルダ(~) とキャレット(^)</a> が参考になります。
以下の一連のコミットで、開発ツール群のインストールと設定を行いました。

フロント側で採用しているおもなライブラリは以下です。

  • React.js
  • redux
  • webpack
  • babel
  • flowtype
  • eslint
  • cordova

あとは Immutable.js があるといいですね。

次は eslint の設定です。現在のメンバーは、この値に強いこだわりがあるわけではありませんが、プロジェクト内で書き方が統一されているのは重要です。eslint は、プログラムファイルを目を細めてみたときの図形としての美しさからセキュリティ上の指摘まで、ずいぶんいろいろみつけてくれるので、機会があれば真剣に検討するとよいのかもしれません。既存実装をすべて修正していただけるなら、.eslintrc.yaml の値を変更するプルリクエストは、おそらく通します。

以下のコミットでは、webpack があれこれした結果をメモリではなくファイルに書き出すようにしています。これは webpack-dev-server のビルド結果に対して cordova build ios を自動でできると開発効率があがるのだが… と思い導入したのですが、まだうまくいっていません。インメモリファイルシステムとかされるとアイディアがわかないので、ハッカビリティだけ提供して放置しています。そのうち消すかもしれません。

以下のコミットで、wewbpack の設定ファイルと本番環境用と開発環境用に分離しています。こちらはそのうち必要になるだろうから、新卒の @takepo さんにお願いするとわりとサクッと作ってくれました。成果は <a href="http://qiita.com/takepo/items/fce9cd7b6742201cddc2" target="_blank" rel="noopener">webpackの設定ファイルを環境ごとに分けるには</a> にまとまっています。なお、このエントリでお手本にしているランサーズのアプリは、まだ本番リリースを行っていません。webpack.config.production.js の内容はそのうち変わるはずです。

/home という URL 付きで、HOME コンテナ関連をつくりました。

/another という URL 付きで、もう1画面つくりました。

以下の一連のコミットで、Qiita の ユーザーリスト API コールを行い、/another の画面で、その結果を表示するようにしました。

次のような特徴があります。どれも定番化していると思います。

  • API コールのアクションを、開始アクション、成功アクション、失敗アクションにわける
  • redux-saga を用いる

API仕様はアンダースコア区切り、JS側はローワーキャメルケース、という状況がよくあります。今回のアプリでは次のようなミドルウェアで対応しています。ミドルウェアでのアクションの選別が、_START とった命名規則に依存しているところが若干気に入らないのですが、まぁいいやと思っています。

navigator.onLine でオフライン通知を行うようにしました。Rails アプリ開発になぞらえると、ミドルウェアはアクションに対する before action 的な使い方ができます。

ログインチェックの仕組みも書いてみました。じっさいのログインチェック自体は割愛します。ここでは HOC(Higher Order Components) を使っています。この場合は、HOC はコンポーネントのマウントに対する before action 的な使い方をしています。

HOC で何ができるのかを知りたければ、<a href="http://postd.cc/react-higher-order-components-in-depth/" target="_blank" rel="noopener">ReactのHigher Order Components詳解 : 実装の2つのパターンと、親Componentとの比較</a> という記事が参考になります。
テスト、型定義、CSS 関連は省略しましたが、ここまでで、私達の React.js/redux アプリでは、どのようなライブラリを採用して、どのような実装を行っているのかの概略を説明しました。すべてはお見せできませんが、とくに尖ったことはしていないし、これといってひどい設計/実装にもなっていません。アプリの設計方針/採用技術の取捨選択の指針として、「次の普通になりそうな新しい技術を、ある程度情報が出揃った段階で積極的に導入したい」と考えています。

私の把握している、現在の課題を列挙しておきます。

  1. saga のテストがない
  2. ジェネレータの型がない
  3. まだ私達は型を使いこなしていないのではないかと不安になることがある
  4. モデリングがてきとう
  5. 忙しくなってきてリファクタの時間が取りにくい

1 は何かとのトレードオフです。 2 は過去に挑戦して難しくてよくわかりませんでした。私にとっては、わりと優先度の低い課題です。3,4 が最大の関心事です。これらは関連しているはずです。プロジェクトの初期は、何かしらの構造をもつデータの型を types/models.js に置いていました。最近は APIコールや WebSokct 通信の payload を types/payload/*.js に必ず書くようにしています。API コールや WebSokct 通信で何が期待するデータの受け渡しなのかが コードからわかって有益です。アクションクリエータの引数のフォーマットも統一されます。もっと大きな改善になりそうなのは <a href="https://www.wantedly.com/companies/wantedly/post_articles/28935" target="_blank" rel="noopener">React使い必見! Immutable.jsでReactはもっと良くなる</a>で紹介されているモデリングだと思います。近いうちに導入したいです。私の観測範囲では、「ビジネスロジックをミドルウェアに追い出す」から「Immutable.Record() でクラス化する」といった発展を感じました。複数アクションに共通する処理なら、ミドルウェアから Immutable.Record() なクラスを使えばよいし、将来的な拡張もふくめて一つのアクションにしか適用しない処理なら、わざわざミドルウェアをつくらず、かわりに Immutable.Record() なクラスを作って、アクションでその Immutable.Record() なクラスを使えばよさそうです。

プロジェクトが進み、APIサーバー側で別チームをつくると、フロント側の開発リソース不足が目立ってきました。ここまでの説明を読みこなせるようであれば、ぜひいっしょに仕事がしたいです。今のところ、フロントエンドエンジニアとしてキャリアを積むのに、魅力的な環境は維持できています。現場からは以上です。