create-react-app と 自前expressサーバをどう組み合わせるか(ビルドの話)

  1. Server Renderingを考えると、server側でもComponent使いまわしたい。
  2. create-react-appでつくった既存のComponentは、Webpackに依存している(CSS Importの部分)
  3. それをserver側でも使えるようにするためには、必然的にserver側もcreate-react-appと同じようなバージョン、設定のwebpackでビルドする必要がある。具体的には下記のような感じ。
webpack
  css-loader
  url-loader
  babel-loader

所感

  • css, imageのimportを諦めれば、server側のビルドはbabelだけで済むのでかなりスマート。けどなんか負けた気がする。
  • react-scriptsを流用してserver側もビルドできないかな?…

qiita.com

Dispatcher, Actions, Constantsのファイル分割粒度とReduceStoreでの注意点

f:id:varmil:20160826081831p:plain

Dispatcher

  • アプリケーション全体で単一のインスタンスで良いのではないか。ファイル分割の必要は特に無いと思う。
  • ただしその場合、生きている全てのStoreが全てのdispatch()を拾うので、下記のようにdefault:でいらないイベントはスルーする必要がある。同一stateをreturnした場合、無駄にre-renderは走らないので特に問題ないという判断だ。
 reduce(state, action) {
        switch (action.type) {
            case AuthConstants.FETCH_MEMBER_STATE:
                return {
                    isPrepared: action.value,
                    isLoggedIn: action.value,
                    isPremium: false,
                };
            default:
                console.info('Not match any actions', state, action);
                return state;
        }
    }
  • しかしfacebookの例では下記のようにドメインごとにdispatcherを分けるようなイメージで作っている。
import type {Action} from './TodoActions';
const instance: Dispatcher<Action> = new Dispatcher();

Actions, Constants

  • 小規模なアプリだったら Actions Constants もそれぞれ単一のファイル(あるいはかなり大きめのドメイン粒度でファイル分割する)に纏めてしまったほうが分かりやすいと思った。理由はそこまでActionの種類は多くないから。せいぜい数十〜百以内には収まるだろうし。
  • ドメインの区別はactionTypeの命名でカバーする感じ。例えば下記のようにTODO_とついていれば「ああ、これはTODOドメインに関するActions Constants なんだなぁ」とわかるだろうし。
Actions

  create: function(text) {
    AppDispatcher.dispatch({
      actionType: AppConstants.TODO_CREATE,
      text: text
    });
  },

  updateText: function(id, text) {
    AppDispatcher.dispatch({
      actionType: AppConstants.TODO_UPDATE_TEXT,
      id: id,
      text: text
    });
    .....
Constants

module.exports = keyMirror({
  TODO_CREATE: null,
  TODO_COMPLETE: null,
  .....
  TODO_UPDATE_TEXT: null
});

flux-utilsを使ったアプリ構築

reduxのほうが勢いはあるけど、基本に忠実で分かりやすいのは facebook / flux ではないかと感じている。

tech.connehito.com

qiita.com

基本構成

  • dispatcher --> fluxリポジトリ内に簡易実装あり
  • stores --> flux-utils
  • container --> flux-utils
  • actions --> 自分でつくる

その他

  • ReduceStorereduce()で、stateを更新する際に工夫が必要?例えば、Immutable.jsを使うとか。

認証をどう作るか

サーバ側でチェックするだけで十分かと思ったが、そうでもない?

frontendinsights.com

FLUXと絡めた話(AUTH0というサービス)

Adding authentication to your React Flux app

stormpathというサービス

Tutorial: Build a React.js Application with User Authentication

基本方針

  • 通信は原則URLチェンジのタイミングでしか行わない。
  • ユーザ権限(非会員、通常会員、プレミアム会員)による

    • 「表示」制御は、React Componentでチェックする
    • 「遷移」制御(URL直叩き)は、サーバ側でチェックする
  • といっても分離するようなものではないかもしれない。つまり、Initial Rendering時にサーバ側で認証状態チェック。NGならリダイレクトとか。OKなら認証状態をpropsとしてcomponentに渡しつつHTML生成。

  • その後の非同期XHR(API呼び出し)に関しては、APIごとに認証状態のチェックをAPIサーバで行うイメージ?とすると、やはりReact Component側では難しいことを考えずに、渡されたpropsをみて「表示」制御を行うだけでいいんじゃないか?

業務アプリでReact.jsを使ってみた話 - Qiita

世の中

  • FLUXなどは特に「URL変更が必ずしも通信を伴わない」という前提があるので、クライアント側(=React Router側)でも認証状態に応じてリダイレクト、のような処理を書いているということかな。

flux + URL設計

検索クエリ

  • flux on url change
  • flux react router

前提

全てのURLはアプリへのエントリーポイントになる。ユーザのほとんどのアクションはURL transition actionのみをdispatchするイメージ。通信は各componentのcomponentDidMount任せ。URLを降ることが出来ない一部のユーザアクション(口コミ投稿など主にPOST系)のみ、別のaction typeを用いるイメージ。

ActionでURL変更専用イベントを用意し、RouteStoreも

  routes: {
    transition: function(path, params) {
      this.dispatch(c.ROUTE.TRANSITION, {path: path, params: params});
    }
  }

-------------------

var RouteStore = Fluxxor.createStore({
  initialize: function(options) {
    this.router = options.router;

    this.bindActions(
      actions.constants.ROUTE.TRANSITION, this.handleRouteTransition
    );
  },

  handleRouteTransition: function(payload) {
    var path = payload.path,
        params = payload.params;

    this.router.transitionTo(path, params);
  }
});

https://github.com/BinaryMuse/fluxxor/blob/master/examples/react-router/app/actions.jsx


Dispatch Routing Action from Store

fluxxor/recipe_store.jsx at d11e52a4e6c99909e9584a8cd84b0a709ffb3d22 · BinaryMuse/fluxxor · GitHub

    // Normally an API call to save a new item would be asynchronous,
    // but we're faking a back-end store here, so we'll fake the
    // asynchrony too.
    setTimeout(function() {
      if (!payload.preventTransition) {
        // See https://github.com/BinaryMuse/fluxxor/pull/95#discussion_r23178351
        // for the reason for this dispatch from the store.
        this.flux.actions.routes.transition("recipe", {id: recipe.id});
      }

データPOST --> 結果返却 --> URL遷移

Code Splitting + Server Side Rendering with react-router and Webpack

Code Splitting + Server Side Rendering with react-router and Webpack

  1. react + react-router + webpackでのコード分割の方法 → わかった
  2. 初期描画前にDBなど非同期操作を行い、その結果を見てデータを返したい場合のサーバ側実装 → わからない
  3. さらに、Server Side Renderingしたい場合 → わからない

2

Componentごとにデータfetchを管理するイメージか。たとえば、componentDidMount のタイミングで各Componentから別途サーバAPIを叩くとか。つまり、initial rendering時には特に何も考えなくて良いのかもしれない。(SSRしない場合)

Load Initial Data via AJAX | React

SSRを考えないと、フローは下のようなイメージ。

クライアントから初期リクエスト
↓
サーバは不完全なHTML, JS, CSSを返却
↓
クライアント側のComponent lifecycle開始
↓
各々のComponentが必要なデータをAPIサーバへfetch

2, 3

ComponentごとのfetchData()を実行する感じ?

Server-Side Rendering with Redux and React-Router | Codementor

  1. When a new request comes in, how do we know which API to call or how do we prepare the application’s state?
  2. After we call an asynchronous API, when do we know the data is prepared and ready to be sent to the client?
1に関して

My personal approach is to stick a static method fetchData() into every routing’s leaf node (if the routing is nested, I’d put it in the innermost one). By doing so, we can use react-router to match the URL to the component we’ll be rendering, and thus calling fetchData() and obtaining the data entry point.

2に関して

fetchData() をPromiseにしよう

注意

initial rendering時だけだが、サーバ側でfetchData()によってデータフェッチを行っているにもかかわらず、その後Client側でも「ComponentDidMount()」が問答無用で走るので、実質同じデータを2回取ってくることになる。これを防ぐには「ComponentDidMount()」の中でdataが既に存在するかチェックするのが良いのでは。と記事の著者はいっている。

調べていて湧いた疑問

reactjsでは通信のタイミングは主に「URLが変わった時」「ComponentDidMount()」 の2種類。自分が今まで書いたものだと、前者のタイミングでしか通信を行っていなかった。FLUXをちゃんと読み直したい。

→ fluxで見ると、XHRはAction (ActionCreators)で行っている。(URL云々は話に出ない)

reactjs - In Flux architecture, how do you manage client side routing / url states? - Stack Overflowにあるように、Flux界隈ではURL Routingのことは議題から除外しているっぽい。

Michelle Tilleyの答え

The strategy is that the URL/routes should determine which components get mounted, and the components request data from the stores based on the route parameters and other application state as necessary.

これは、俺の思想と同じ。 ユーザアクションによって、アプリのステートが変化するならば、それを必ずURL更新に還元してあげる。URL更新のハンドラで通信を行うイメージ。

webpack 2.0 以降なら System.import が使える

Code Splitting for React Router with ES6 Imports - Modus Create

ディレクトリ構成

Implicit Code Splitting and Chunk Loading with React Router and Webpack | Henley Edition