背景
React+Redux使ってみていろいろ悩み事が出てきたので書き連ねる。
選定理由
- react良さそう
- 画面変更の度にDOMをごりごりいじるのはやめよう。
- あるべきUIを定義しておけばよいという世界はわかりやすい。
- でもアプリ全体としての動きのフレームワークにはなり得ない。
- Flux良さそう
- データフローが一方向なのは回りくどい感もあるが、処理の流れとしては追いやすそう。
- Flux実装どんなのあるかな?と探してみたら例のごとく群雄割拠
- flummoxの作者曰く、"4.0 will likely be the last major release. Use Redux instead. It's really great."
- なんとなく自分の観測範囲でもreduxが良さそうな雰囲気
- 開発ペースも良いし、ドキュメントも最初からかなり気合い入っているので期待できそう
- 全部英語だけと
だいたいこんな感じで決めた。
Redux自体についての解説はざっくり言うと、
- アプリの状態遷移を決める
reducer
- actionから受け取ったユーザ情報をユーザリストに追加する
- アプリの状態は唯一の
Store
で全て管理される
- 状態遷移のトリガーとなる
action
- APIからユーザをfetchしたことをreducerに伝える
- reducerによって定まった状態を表示するView(何でも良いが、Reactと相性が良い)
- 更新されたユーザリストを表示する
の3つの登場人物を使ってアプリを作るフレームワーク。
MVCやMVVMのようにModelやView、ViewModelの概念は出てこない。(フレームワークとしては規定しない)
Controller的なものもない。アクションが起きて、それを受けてアプリの状態が変わり、状態変化をトリガーにして最新状態がViewに反映される、ただそれだけ。
良いと思ったこと
Viewとアプリの状態遷移の分離
UI(だいたいReactだと思う)とアプリの状態遷移(Reduxのreducer)が完全に分離するので、並行開発がやりやすい(MVCモデルでもそうかもしれないが)。実際、UIはなかなかfixされなくても状態遷移として必要な処理はある程度決まってくるので、UIの仕上がりを待ちつつバックエンドのreducerやactionの実装を進められる。
感覚的にはサーバサイド実装をしている感じに近い。データの変更はすべてreducer(サーバだとcontroller + model的な感じ)で行い、View側には表示すべき情報だけを選んで渡す(railsでインスタンス変数に必要な値をセットしたりするよね?)。ViewのReact側では、定義されたViewの仕様に従い仮想DOM(erbとかhamlとかslimとかでインスタンス変数使って画面組ますよね?)を組み、描画する。
テストしやすい
アプリの肝となるreducerは純粋関数(じゃないといけない)なので、引数から出力が決まる関数のテストとして書ける。実際、mochaでがりがり書いている。
状態遷移を追いやすい
アプリの状態遷移はreducer以外では起きないので、viewで勝手に値が変ったり、あるいはactionの中で値が変ったり、ということは基本的にはない。これはreduxというか、fluxならば、という特徴。
工夫が必要な事
アプリの状態をどう定義するか?
ここをどう考えるかがけっこう大事。reducerがアプリの状態遷移を全て管理するので、アプリの状態をうまく切り分けないとreducerの実装が膨らんで複雑になってくる。たとえば、
articles: {
id: 1,
author: {
name: 'hokuma',
registered_at: '2015-09-09 ...',
,,,,
},
comments: [
{
id: 123,
message: 'hogehoge',
,,,
]
title: 'Title',
,,,
}
というような情報をサーバから取得したときに、これを全てarticleの状態を管理するreducerで扱おうとすると、author情報が複数articleにまたがるかもしれないし、コメント関係の処理をするにはまずコメント元のarticleを取得して、しかるのちにコメントの処理をする、と言った感じで処理が複雑になってくる。
けっきょくのところ同じデータが複数箇所に出てきたり、ネスト構造をしているのが諸悪の根源なので、データ構造のネストをNGとして、エンティティごとに分割したフラットなデータ構造に展開するのが良さそうだと思っている。
以下のmoduleを使うと、APIレスポンスを良い感じに正規化してくれるので、この生成結果をベースにreducerのデータ構造を組めばネストがなくなり、ネストだったところは他のエンティティへの参照になる。。
https://github.com/gaearon/normalizr
normalizrを使ってAPIレスポンスをうまくフラットにした上で適切な情報を適切なreducerで扱うようにすれば、ネストした物を丸っと扱うよりかはreducerの実装をシンプルにすることができそう。
Immutable.js良い感じ
状態が変ったら必ず新しいオブジェクトに生成する必要があるので、Immutable.jsとあわせて使うと良さそう。Reactでもオススメされている。
懸念点
ベストプラクティスが揃ってない
JS界隈特有な気もするが、いまいちこれで良いのかわからない感が強い。
パフォーマンス
アプリの状態が変るたびにコンポーネントのrender処理が走る。仮想DOMに変更がなければ実際のDOM操作は走らないものの、仮想DOMを作る処理は走る。今のところ大きな問題にはなっていないが、必要に応じてReactコンポーネント側でshouldComponentUpdate
を上書きして余計なrender処理を省くなどが必要かもしれない。
UIモックアップとのギャップ
Reactを使うからにはDOM操作のためにjQueryとおさらばする必要がある。特に、モック画面を作るのがデザイナーさんとかだと、jQuery pluginをがんがん使って画面を作る事もあると思うので、これをReactで実現する辛みがある。良いReactのコンポーネントがあれば良いが、なかった場合には自分でReactで実装しなければならない。
http://react.rocks/
などサンプル集などはあるが、見つかったコンポーネントがReact0.14でサポートされてないこともある。
railsとの相性
Redux+Reactで作る以上、JS処理的なもの絡むところは基本的にはredux(というかReact)で描画することになる。
つまり、初期表示が空になる。ファーストビューが空ってどうよ?ということであれば、UIの一部はrailsのviewで書くか、react-rails
を使ってserver-side renderingをするなどをした上で、改めてクライアント側でレンダリングすることになる。
な記事もあるが、reduxのProvider以下の画面においてうまくいくか不明。