create-react-app と 自前expressサーバをどう組み合わせるか(ビルドの話)
- Server Renderingを考えると、server側でもComponent使いまわしたい。
- create-react-appでつくった既存のComponentは、Webpackに依存している(CSS Importの部分)
- それをserver側でも使えるようにするためには、必然的にserver側もcreate-react-appと同じようなバージョン、設定の
webpack
でビルドする必要がある。具体的には下記のような感じ。
webpack css-loader url-loader babel-loader
所感
- css, imageのimportを諦めれば、server側のビルドはbabelだけで済むのでかなりスマート。けどなんか負けた気がする。
- react-scriptsを流用してserver側もビルドできないかな?…
Dispatcher, Actions, Constantsのファイル分割粒度とReduceStoreでの注意点
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; } }
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を使ったアプリ構築
認証をどう作るか
サーバ側でチェックするだけで十分かと思ったが、そうでもない?
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
をみて「表示」制御を行うだけでいいんじゃないか?
世の中
- 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
- react + react-router + webpackでのコード分割の方法 → わかった
- 初期描画前にDBなど非同期操作を行い、その結果を見てデータを返したい場合のサーバ側実装 → わからない
- さらに、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に関して
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 callingfetchData()
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