React.jsと組み合わせるfluxのライブラリreduxを試してみた。 このページは作業ログです。やったことをつらつら書くだけなのでまとめません。あしからず。
対象読者
- 主に自分
ゴール
- 最近のJavaScript開発環境に馴染むこと
- reduxを使ったアプリケーション開発がどんなものか理解できていること
reduxを試した理由
- React.jsと組み合わせるfluxの中で最右翼
- 今作っているSPAアプリがある程度複雑になりそうなのが見えているため
- (Mithrillくらいシンプルな方が良さそうな気がする)
やったことメモ
node.jsの更新
まずはnodeを更新しようと思い、下記のコマンドを実行。
$ brew update
するとError: uninitialized constant Formulary::HOMEBREW_CORE_FORMULA_REGEX. Please report this bug: ...
と表示された。Homebrewのページによると、もう一度brew update
すればいいらしい。
$ brew update
$ brew upgrade node
$ node -v
v0.12.7
開発に必要なツール群のインストール
-
babel - ES6のトランスパイラ。reduxのexampleですらES6で追加された
import
文が登場するので必須。babel-node
というES6で書かれたスクリプトをnode.js
で動かすラッパーもある。 - webpack - JavaScriptに限らずHTMLやCSSも含んだあらゆる依存を静的なassetに合成するツール。リンク先の解説ページによると画像なんかも埋め込める。
babelのインストール
$ npm install -g babel
これでbabel-node
もインストールされた。
webpackのインストール
$ npm install -g webpack
reduxのexampleを動かす
リポジトリのclone
$ git clone git@github.com:rackt/redux.git
$ cd redux
リリース版をcheckout
masterは開発版であることが多く、安定していないかもしれないので、タグを見てリリース版をcheckoutする。githubのリポジトリを見てもよさげ。
$ git tag
$ git checkout v1.0.1
exampleのビルド
JavaScriptだからbuild不要だと思ってたら、ES6で書かれているから変換が必須。bundle.js
$ npm run build
# なにかエラーがあったら「コマンドが足りていない」等なので適宜対応する。
exampleを動かす
$ cd examples
$ ls
async buildAll.js counter real-world testAll.js todomvc
v1.0.1時点では下記の4つがexampleにて公開されている
- async : redditから指定したタグのコンテンツを取得し、一覧作成
- counter : ボタンを押した回数を数えるカウンタ
- real-world : GitHubの情報を取得して表示
- todomvc : TodoMVCのredux版の実装
async
、real-world
はredditやGitHubに接続するので、ネット環境がない場合は動作しない。
ドキュメントを読んでみる
Reduxに関するドキュメントは、racktというReact.jsのコミュニティにドキュメントが公開されている。
平易な英語で書かれているので読みやすい。
Reduxを一言で言うと、予測可能な状態を作るための入れ物(Redux is a predictable state container)だそう。読み進めていくにつれ、確かにそうだと思った。
ところでタイムトラベル(TimeTravel)ってなんぞ? Undo/Redoとか特定の時点での状態を取り出せるってこと?
1.1 動機
MutationとAsynchronousの2つからくる複雑さを解決するためのライブラリがRedux。アプリケーションが持つ状態はMutableだし、それが非同期(Asynchronous)に変わるからアプリケーションが複雑になりがちだから、作ったらしい。
1.2 3つの原則
- 真実は1つ(Single source of truth) : 唯一のstoreオブジェクトにアプリケーション全体の状態を持たせる
- 状態は読み取り専用(State is read-only) : 状態変更には明確に何が起きるのかわかるactionを使う
- 状態変更は関数で書く(Mutation are written as pure functions) : 状態のオブジェクトツリーがどう変わるのかを明確に表現するためにreducerを実装する
1.3 既知の技術(Prior Art)
Reduxを実装する際、参考にした既存技術がまとまっているみたい。
- Flux : ReduxはFluxの実装ではあるが、Fluxではない。Dispatcherにはコンセプトがないのと、Stateオブジェクトを必ずImmutableになるよう扱うところがFluxとは違うらしい。Fluxよりもシンプルで十分なアーキテクチャに見える。(私見)
- Elm : ある状態+アクション=次の状態
(state, action) => state
というアーキテクチャを推奨した関数型プログラミング言語 - Immutable : JavaScriptのオブジェクトのデータ構造を永続化するライブラリ。
- Baobab : 大体同上
- Rx : リアクティブプログラミング!
2. Reduxの基本
Redux自体はFluxパターンの実装という位置づけのためReactに依存していない。ビューにEmberでもAngularでもjQueryでもいける、と書かれている。そのため、このドキュメントはReact以外の部分から解説が始まる。
- Actions : Storeに送るデータ。Fluxとは異なり、副作用を作らないためにJavaScript Objectを返すだけの実装。
- Reducers : Actionによって起きたイベントからアプリケーションの状態を変更する。アプリケーション全体のスコープで管理するstateオブジェクトを適宜分割して状態を管理するのがRedux way。
- Store : Storeはアプリケーションの状態の管理とActionが起きた時のイベントを受け取る。イベントを受け取った時に通知するListenerも登録できる。
- Dataflow :
- Using with React : ここでReactとの組み合わせを解説
2.1 Actions
アプリケーションからイベントを受け取り、Storeにデータを送る。イベントの種別毎にtype
を定義する。
2.2 Reducers
実装順は下記のように考える。
- アプリケーション全体の状態オブジェクトを設計する
- アクション毎の状態の操作をプロパティ毎に実装する
-
combinedReducer()
でそれぞれのプロパティ毎の操作を行うfunctionをガチャコンする
2.3 Store
Storeクラスが担う責務は下記の4つ
- アプリケーションの状態の保持
- 状態へのアクセッサ : getState()
- 状態を更新する : dispatch(action)
- リスナの登録 : subscribe(listener).
Storeの作成createState()
の最初の引数はReducerで必須。初期状態があるのであれば、2つ目の引数で受け取る。
StoreオブジェクトがReduxの中心的なオブジェクト。
2.4 Data Frow
Reduxのアーキテクチャは双方向にデータをやりとりしないことを厳格に採用している。それにより、データのライフサイクルが全て同じようになる。Reduxのライフサイクルは下記の4つ
- store.dispatch(action)を呼ぶ
- reducerを実行し、stateを更新する
- 複数のreducerをまとめたroot reducerを実行し、stateのオブジェクトツリーを更新する
- storeは更新されたstateのオブジェクトツリーを保持し、Listenerに通知
2.5 Reactとつなげる
ReactのComponentとバインディングするモジュールをインストール
$ npm install --save react-redux
SmartとDumbの2種類のコンポーネントがあるが、実装ではあまり意識しない。コンテナはSmartコンポーネント、個々の要素はDumbコンポーネントと考えれば大体OKっぽい。
1日目で学んだこと
- redux は思ったよりも簡単そう
- Actionを定義し、Action毎にreducerを定義し、storeを変更していく流れはわかりやすい
- webpackやReactの方を知らなさすぎる。特にReactでComponentのイベントハンドラからActionを作る流れがわからない
- reduxのstateの変更をComponentに渡していくあたりがよくわからない
- reduxのボイラープレートを使って簡単なアプリを組みたい