概要
redux 初心者による redux 解説です。ちなみにですが react も初心者です。flux は使ったことないです。
この記事は、redux の解説記事とかいくつか読んだけど、イマイチ腹落ちしないなー、と感じている人向けに書いています。なので、そもそもredux について全く知りません、という方はまずは以下の記事などを読んで貰えればと思います。
http://qiita.com/kiita312/items/b001839150ab04a6a427
http://redux.js.org/docs/introduction/index.html
react に寄り道
redux は react とセットで使われることが多いようなので関係する部分を説明します。
react は Model ⇒ View のフレームワークです。Model に対して View が一意に決まります。いつ何時でも、Model が同じであれば View も同じです。Model が同じなのに View の状態が変わると、View が状態を持っていることになります。誤ってこういう作りにしてしまうと、Model だけでなく View の状態も意識してコードを組まなければならなくなり、無駄に複雑になってしまいます。
ちなみにですが Angular(v2 は使ったこと無いので v1 の知識で書いています)は Model ⇔ View の双方向でデータが流れます。Model の状態によって View が決まる、という点では react と同じですが、View を変更することで Model を直接変更することができます。初めて使った時は感動しました。View を変更すると自動的に Model が変更されるので、View の変更を Model に反映させるためのコードが不要になり、コード量が劇的に減ります。素晴らしいことです。でも、画面や Modelが複雑になると少し話が変わってきます。View から Model が直接変更されるため、Model の状態管理が難しくなるのです。例えば、Model の構造と View の構造が一致していないケースを考えてみます。どういう時にこういうことが起こるかと言うと、Model をデータベースのテーブルと同じ構造にして、View は別の構造にしている場合です。こういった場合、View からデータを変更する際、Model 用の構造に変換してする必要があります。しかし、こういった変換処理に誤りがあると、Model と View の状態が一致しないことがあります。つまり、Model ⇔ View の双方向バインディングは、「Model(状態A)」 ⇒ 「View(状態A)」 からView を編集して 「View(状態B)」 に更新した際に、Modelの更新を誤って「Model(状態C)」 というような状況になる可能性があります。
react は Model ⇒ View の一方向なので、上記のような矛盾が発生しません。ただ、View ⇒ Model の部分の開発が必要になります。これはワリと面倒です。ただし、シンプルなのでコードの可読性は上がります。なので、簡単なアプリは Angularで、複雑なアプリは React で、という様に使い分けるのもありだと思います。
話が大分横道に逸れましたが、redux について説明します。
redux とは
redux と react を組み合わせるという場合、Model ⇒ View の部分に react を使います。View ⇒ Model や Model の状態管理を redux が担います。
redux の登場人物は以下です。
- store:
Modelを管理します。
redux では Model を state と読んでいるので、以下では state に統一します。 - action, actionCreator:
stateを更新するイベント(action)を定義します。 - reducer:
stateの更新処理を定義します。
View ⇒ state への反映処理が redux の特徴だと思います。例えば入力フォームを編集したとします。この際、store から state を取り出して変更する・・・ということはしません。フォームを変更したよ!という通知をするための action を作成します。action にはイベントの種類(フォームの変更)と変更後の値(入力値)を持たせます。この action を store に渡します。store はそれを reducer に右から左へ流します。その際、state も渡します。reducer は action と state を受け取り、action の内容を反映した新しい state を作成し、store に返します。store は返された state を保持します。そして、その新しい state を react に連携します。react は state に基づいて View を変更します(この例の場合は View の変更はないですが)。
このように、View を変更した際に直接 store を更新するのではなくて、変更内容を action にセットして連携するようにすることで、「state を更新するトリガー」が分かりやすくなります。action の作成は actionCreator で一元管理します。
しかし、ここで初心者の私には疑問が湧きました。「reducer 要らなくない??」
reducer で行う処理は、「旧state と更新内容を受け取って、新しい state を返す」ですが、この機能は store に持たせればいいんじゃないか?と思いました。でも、一度アプリを作ってみて、reducer の利点が分かりました。まず、reducer で state の更新処理を行うことで、「store の内のあちこちで state をぐちゃぐちゃ変更して、更新処理の全体像が見えなくなる」というようなことを回避できます。reducer は「旧state と更新データを受け取って、新しい state を返す」という非常にシンプルなインタフェースになっているため、「state がどのように更新されるか」ということが詳らかになるわけです。また、redux は state の階層構造への対応として、階層別に reducer を作成できるようにしています。つまり、state = {A:value, B:{B1:value, B2:value}} というような構造になっている場合、state.A は reducerA で更新し、state.B は reducerB で更新する、というようにできます。これにより、state が複雑であっても、state の個別要素の更新処理を独立して実装できるため、更新処理を簡潔にできます。
おまけ: 外部リソース(WEB API など)からデータを取ってくる場合
reducer の処理は pure でなければなりません。reducer の「旧state と更新データを受け取って、新しい state を返す」処理は、引数が同じであれば、いつ何時でも同じ値を返さないとダメです。従って、reducer の中では、外部リソースへのアクセスを絶対に行ってはいけません。
「じゃあ API へのアクセスとかはどこでするんだ?」と思うかもしれませんが、結論から言うと、actionCreator で行います。actionCreator 内で API からデータを取得し、それを action にセットして store に渡します。これにより、reducer の処理が簡潔になる、つまりは state の更新処理が簡潔に保たれるのです。
まとめ
redux を勉強し始めた頃、概念やメリットを余り理解できませんでした。また、アプリを作っている時も、「ちょっとしたフォームの編集処理でも、コード量が多くなるなあ・・・」と思っていました。しかし、作り終わってコードを見直してみると、可読性が非常に高いことに気がつきました。
redux は、複雑な処理を簡単な処理の組み合わせで実現します。そのため、一つ一つの処理はとても分かりやすいものになります。一方で、コード量は減るどころか、寧ろ増えます。でも、コードは書く回数よりも読む回数の方が多いことを思えば、可読性が高いというのは極めて大きなメリットだと思います。
redux、ありですね。
ただ、redux の思想や利点を理解して使わないと良さを活かせません。この記事が redux 初心者の学習の一助になれば幸いです。