ReactとReduxを一通り触ってみた

Flutterのアーキテクチャ組むとしたらReduxだよね、みたいな話を聞いたので、Reduxのお勉強がてらReactやってみるかという程度の軽い気持ちではじめてみました。忘れないうちに備忘録としてまとめておきます。

React

最近、というのも憚られるほどjs系のライブラリが枯れてきている今日このごろですが、Reactです。単体ではデータフローなどには全く関与せず、純粋なViewを管理するライブラリです。特徴としては大きく2つあります。

Virtual DOM

名前の通り、ブラウザが実際に保持しているDOMとは別のReact内部の仮想DOMです。

f:id:t-miliya612:20180610201857p:plain
https://www.html5rocks.com/ja/tutorials/internals/howbrowserswork/#The_rendering_engine

何でそんなことをするのかというと、ブラウザレンダリングの高速化のためです(超ざっくり)。下の写真はブラウザがhtmlとcssを解析・描画していく流れを示したものです。 このうち、特に重い処理がLayoutPaintingの2つで、これが行われる回数・範囲を如何に減らしていくか、というのが課題になります。
初回のレンダリングは仕方ないですが、ユーザーのアクションや外部からのpush通知などのイベントに応じて一部を描画しなおしたりする際、変更があった部分だけを再描画してあげれば、先の2つの処理による負荷を小さくすることができます。
VirtualDOMを使って事前に現在のDOMとのdiffを取ってあげることで、これを実現しようというわけです。

JSX

Javascriptを拡張したXMLライクな言語で、これを使って描画するコンポーネントを記述していきます。

<div className='container'>
  <h1 className='title'>Hello</h1>
</div>

htmlとは少し文法が異なりますが、大体読めばわかるかな、という感じです。rubyerbのような、ただパースして処理をしてくれるもの、とはちょっと違う感じです。
また、Reactにおいてjsxの使用は必須ではありません。現に、reactの入門書として有名(?)なオライリーのこの本はjsx無しで書かれてています。
余談ですが、vimでjsxのシンタックスハイライトを行う際、htmlとして認識させるのが手っ取り早いやり方みたいです。なるほど、確かに。

実践

この本をやってみました。

React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上まで (NEXT ONE)

React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上まで (NEXT ONE)

扱ってるテーマはすごくいい感じで、これに沿って公式ドキュメントを読み進めていくといいと思います。誤字がちょっとあるのと、扱ってるmaterial-uiのv1.0がリリースされてだいぶ変わってしまった点を除けば良書でした。

material-ui.com

この本で実装したYahoo! shoppingAPIを利用したアプリケーションです。特に本以外の面白いことはしてませんが、一応添えておきます。

github.com

Redux

Reduxは、Fluxアーキテクチャを実装した(と言われていたりいなかったりする)フレームワークです。

flux

f:id:t-miliya612:20180610202050p:plain
https://facebook.github.io/flux/docs/in-depth-overview.html#structure-and-data-flow

facebook.github.io

公式のこの図が有名ですね。やりたいことは、データの流れを一方向にしか流れないように制御することです。
このサイトによると、

This structure allows us to reason easily about our application in a way that is reminiscent of functional reactive programming, or more specifically data-flow programming or flow-based programming, where data flows through the application in a single direction — there are no two-way bindings.

データ管理が双方向にならない分、アプリケーションのロジックはシンプルにデータの流れを記述するものになるんですね。reactiveって単語が出てくるのも納得です。

登場人物

  • View
    • Reactのコンポーネントやhtmlなど、ユーザーにデータを表示したり、ユーザーからのイベントを受け付けたりするもの
  • Action
    • Viewで発生したイベントを通知するメッセージそのもの
    • いわゆるSOAPのリクエストのようなもの?
    • ActionCreatorを通じて作成される
    • typeプロパティが必須
  • Dispatcher
    • 全てのデータフローの中心のハブ
    • Actionを受け取り、Storeへと転送する
    • 全てのデータの変更はDispatcherを経由してStoreへと送られる
  • Store
    • State(アプリケーションで保持するデータ/状態)を管理する
    • DispatcherからdispatchされてきたActionを受け取り、stateを変更する

Redux

github.com

Fluxの流れに従いながら、少し登場人物が増えたり原則が増えたりしています。 Reduxには3つの原則があるのですが、先に中身の方からまとめていきます。

Fluxとの違い

公式サイトに言及がありました。

Unlike Flux, Redux does not have the concept of a Dispatcher. This is because it relies on pure functions instead of event emitters, and pure functions are easy to compose and don't need an additional entity managing them.

  • ReduxにはDispatcherという概念がない
    • ReduxにはEventEmitterよりも純粋な関数であるということが重要(超訳)
    • 副作用がないため管理が楽

Another important difference from Flux is that Redux assumes you never mutate your data. You can use plain objects and arrays for your state just fine, but mutating them inside the reducers is strongly discouraged. You should always return a new object, which is easy with the object spread operator proposal, or with a library like Immutable.

  • データを決して変更してはならない
    • 毎回新しいオブジェクトを生成して返す必要がある
      • object spread operatorなどを使おう

これは状態管理という側面もありますが、Stateが変更されたことをObserveする際の都合もあります。Stateの変更は元のデータと最新のデータが異なっている、ことで検知されるため、元のデータを書き換えてしまうとObserveできなくなってしまうんですね。

これを踏まえた上で、ReduxではDispatcherの代わりにReducerを使用します。

3つの原則

  • Single source of truth
    • Stateは1箇所で管理する
    • fluxの場合は数まで言及されていないみたい
  • State is read-only
    • Actionを発行する、以外の方法でStateを書き換えてはならない
    • Viewからいきなりstateにsetterで書き換えとかやっちゃだめ
  • Changes are made with pure functions
    • 先に書いた通り

middlewareの概念

Reduxにはmiddlewareという概念があります。例えば、loggerや非同期処理といったものを実現してくれるもので、ActionCreatorで生成されたActionがReducerに渡る前に処理してくれます。

全体像

これまでの内容を踏まえると、以下の図が読めてくるんじゃないかと思います。

ちょっと脱線

ここまで読むと、「原則としてはきれいだけど、じゃあHTTP通信の処理とかどこでやんのさ」という気持ちが湧いてきますね。ReduxをMVPのView部分だけに押し込めて残りは別でやっていく、というのも考えましたが、何かあまり上手いやり方ではない気もします……。

qiita.com

qiita.com

超簡単に要約すると、redux-sagaというMiddlewareを使ってtaskという概念を作り出し、ActionがReducerにわたる前にやっつけてあげよう、というわけです。詳しくは上の記事や、公式リポジトリをどうぞ。

github.com

おわりに

ここ数年reactに苦手意識を持っていたのですが、思っていたよりもシンプルで使いやすそうでした。ほんのちょっとstaticなページを作るのにはやりすぎかな、という感じもしますが、client側でここまでデータの処理ができてるからこそAPIの提供者側はきれいなIFを定義できるんですね。
一方。。。

AndroidかGoかという環境で生きていると、Androidの超リッチなIDEやGoのformatterの恩恵に甘えまくってしまいます。いざjs書こうとなると。。。
今の所Vimeslint入れて書いていますが、formatterは欲しいですね。