『実践 Redux Saga』 – React, FLUX, Redux, Redux Saga – // 第21回社内勉強会 #sa_study

20170127-practical-redux-saga

 

こんにちは。エンジニアの吉田です。

2017年1月27日(金)に開催された社内勉強会#21で、『実践 Redux Saga』というお話をしたのでブログにもまとめたいと思います。

 

スライドはこちら。(Slideshareにアップするとフォントが欠落してイマイチな見た目になってまう…)

実践 Redux Saga -Practical Redux Saga- from Shinichiro Yoshida

 

要約

  • プロダクションの案件でRedux Sagaを導入したのでそのお話
  • Redux Sagaの話の前に、jQuery、React、FLUX、Redux をおさらい
  • Redux Sagaを使うと非同期処理を取り扱えるが、その反面、データフローが複雑になる
  • System&User Action と Reducer Action を分けることで複雑性を軽減した
  • Action の命名規則、React, Redux, Redux Saga の責務を明確にするガイドラインをつくった
  • みなさまがより快適なフロントエンドライフをお送りいただけることを願っている

 

はじめに

プロダクションの案件で redux-saga を採用してみたので、そのお話をします。

どのような指針でアーキテクチャ設計をしているか、なぜそのような指針にしているか、そのような指針で進めた結果、何が見えてきたか、といった感じの内容です。

設計思想や考え方の話が多く、抽象的な部分も多いかと思いますが、ご承知おきください。

 

技術要素

React , Redux , Redux Saga , Apache Cordova , Onsen UI , Sqlite , WebSQL , WebAPI(Ajax) , Webpack , Babel(stage-0), ES6 , Generator(ES6) , Promise(ES6), Gulp, Sass あたりの技術を使ってます。

React , Redux , Redux Saga あたりが今回お話しする内容です。

jQuery, React, FLUX, Redux とおさらいをしたうえで、Redux Sagaの話に入ります。おさらいが長いけどご容赦を。

 

jQuery

まず、jQueryからおさらいを。

jQueryは、シンプルな構成のサイトで使う分には全く問題ありません。

静的なHTMLコーディングで要件が満たせるサイトに対して、アコーディオン、簡易的なインタラクション、タブ切り替え、ちょっとした外部APIコール程度なら。

でも、DOMの変更を多用すると、どこで何が起きているかわからなくなっていきます。

外部からデータを取得して、HTMLの断片をテンプレートとして定義して、データをテンプレートの文字列を置換して、画面に表示して…

append()、appendTo()、innerHTML …

 

スライド33

 

  • その間にユーザーが別の操作をしたらどうなる?
  • 外部からデータが取得できなかったらどうなる?
  • データ呼出しのボタンを連打されたらどうなる?
  • 別のデータを取得してデータ結合してから画面を表示する場合どうなる?
  • DOMの構造に依存した操作をするタイミングで、既にDOMの構造が変化していたらどうなる?
  • CSSクラス名を付けたり消したりするタイミングが重複したらどうなる?
  • イベントリスナー監視しまくりのコードを自分が引き継いだら理解できる?

 

スライド34

 

もうよくわかりませんし、わかりたい気力も生まれません。

 

そこで、Reactを使うことで、 いくつかの課題を解決できます。

 

React

React は、ユーザーインターフェイスを構成するためのフレームワークです。

考え方は、サーバーサイドでレンダリングしていたWebアプリケーションに似ています。

たとえば、PHPでサーバーサイドのWebアプリケーションを開発する場合、「モデル」「ビュー」「コントローラ」という3つのレイヤーに構造を分離したうえで開発をします。

コントローラが処理を受け付け、モデルがデータを取得し、ビューが画面をレンダリングします。

React はMVCパターンのにおける「ビュー」の役割を担当しているフレームワークといえます。

 

 

Reactでは、Component という単位の パーツを組み合わせて HTMLのDOM構造を構築します。

Componentのクラスの中で JSXというHTMLっぽい記法で DOMの構造を定義できます。

スライド42

 

HTMLの属性と似たように Propsという属性も定義できます。

スライド45

 

Component は Propsのほかに、 Stateとよばれる状態を 保持することもできます。

スライド48

 

つまり、画面のDOMの構造は “Props”と“State” の状態によって、決まります。

スライド50

 

例えば、Stateの状態が変化すると…

スライド56

 

Stateに依存する部分のDOMが更新されます。

スライド57

 

JSXとStateとPropsによって、HTMLのDOM構造が決定します。

スライド59

 

DOMの構造は、Reactが変更を自動的に計算して、差分だけを更新してくれます(Virtual DOMという機能)

スライド60

 

Component は部品ごとに細かく分けてネストすることもできます。

スライド62

 

Componentはツリー上の構造になるので、親から子へデータを渡す場合はPropsで渡していきます。

スライド63

 

子から親へのデータの受け渡しは、コールバック関数を経由してやり取りします。

スライド64

 

と、ここまでがReactのお話で、次にFLUXのお話をします。

 

FLUX

 

FLUXはアプリケーション構造のアーキテクチャに名付けられた名称です。

Facebookのエンジニアにより、2014年のF8のセッションで説明されたものです。

 

 

これは、MVCだと構造が複雑になるとスケールしないという問題を解決するために考えられたものです。(ModelとViewのデータフローが双方向になりがちで、コードの影響も予測しにくくなりがち。)

スライド69

 

そして、親と子のコンポーネントのデータのやり取りも、データフロー双方向になると、コードの影響が予測しにくくなります。

スライド70

 

FLUXは、データフローを単方向にすることで、コードの影響を予測可能にする考え方です。

スライド73

 

FLUXでは、Action、Dispatcher、Store、Viewと分かれていて、以下のような役割を持っています。

スライド74

 

これが、さっきのReactのイメージだとして、

スライド50

 

StateがComponentの外にでて、

スライド76

 

StateがStore、ComponentがViewという役割を持つことになります。

スライド77

 

で、こんな感じのデータフローになります。

スライド78

 

と、FLUXはこんな感じで、データフローが単方向のアーキテクチャというわけです。

そして、ここから発展して生まれてきたのたReduxです。

 

Redux

Reduxは、 FLUXElm Architecture に影響を受けた ステート管理コンテナと呼ばれています。

とか言われてもよくわかりませんが、FLUX の発展形で、いくつかの制約が加わった感じの考え方です。

 

どいう制約が加わっているかというと、

  • 「Storeは、ひとつだけ」
  • 「Stateは、原則、読み取り専用」
  • 「状態の変化は、Reducer経由で」

という感じです。

そして、Dispatcher という概念もありません。

 

新しく Reducer という概念がでてきましたが、Reducerは、Stateを更新してくれる役割を持つものと思っていれば大丈夫です。

 

おおまかに、Action、Reducer、State、という3つの役割を知っておけばなんとかなる。そう、君ならね。

スライド97

 

ちょっと口語調が混ざってしまいましたが、さっきのFLUXの図をもとに説明すると、

スライド98

 

まず、Dispatcer はありません。代わりに Reducer がいます。

スライド99

 

そして、ReducerとStateは、Store という括りにまとまって、Storeはひとつだけ存在します。

スライド100

 

これをもうちょっと分解してみると、

スライド101

 

こんな感じになって、

スライド102

 

左側が Redux、右側が React という役割分担の図になります。

スライド103

 

という感じで、それぞれの役割の呼び方はちょっと違うけれど、流れは FLUX と似てますよね。

 

そして、 Redux が FLUX と決定的に違うところは、 Middleware という機能を持っている点です。

これは Redux の大きな強みで、プラグインのような感じで Redux に振る舞いを追加することができるようになります。雰囲気でいうとAOPっぽい感覚と似てるような気もしますが私だけでしょうか。

スライド107

 

Middlewareを使うことで、外部API呼び出しなどの 非同期処理の取り扱いが、比較的シンプルに実装できるようになります (Middlewareは「副作用」とか呼ばれたりもします。)

スライド109

 

そして、このMiddlewareは世の中にたくさん公開されているのですが、その一つが Redux Saga です。

 

Redux Saga

ここでやっと本題に辿りついたんですが、前提となるお話を進めるだけで、本当にくたびれますよね。でも、めげずに続けます。

 

さて、Redux Sagaを使うと、任意のActionを受け取って、任意のロジックを非同期で処理することができるようになります。例えば、外部API呼出しとかですね。

スライド113

 

そして、Sagaからは、別のSagaを 呼び出すこともできたりします。

スライド115

 

Actionを発行したり、 受け取ったりすることもできます。

スライド117

 

で、実際のところ、どう使っていくのがいいんじゃ?という話になるわけですが、Sagaは自由に組み合わせられる反面、人によって好き勝手に実装できてしまうので、これはある程度のガイドラインを決めたいところです。

 

いろいろ模索してみた結果、下記の記事がとても参考になったので、これを採用することにしました。(画像クリックで該当記事にリンクします。)

スライド123

 

この記事では、Actionを下記の2種類で分けて考えるという提案が書かれていて、Sagaの前と後で発生するActionを明確に分けるということなんです。

  • 「System&User Action」
  • 「Reducer Action」

 

どういうことかというと、下記の図だと、Container から Action が2つに分岐しているんですが、

スライド127

 

その分岐をなくしてしまって、

スライド128

 

すべて、Saga を経由させたらいいじゃない、という感じの考え方です。

スライド130

 

そして、View から発行される Action を 「System&User Action」、Saga から発行される Action を「Reducer Action」と呼ぶことにしています。

  • 「System&User Action」 … ユーザーが操作したアクション or システムから発生したアクション
  • 「Reducer Action」 … Reducer へ渡すための State 更新指示をするアクション

 

ここで大事になってくるのが、それぞれの Action の名前です。せっかく Action の役割を分けられても、Action 名に統一性がなければ、理解しにくくなってしまいます。

なので、下記のような命名規則をガイドラインとして決めることにしました。

  • 「System&User Action」 … 「だれが、何を、どうした」 
  • 「Reducer Action」 … 「Sagaが、何を、どうした」

 

例えば、「System&User Action」だと、

  • USER_NEWS_ARTICLE_TOUCHED (ユーザーがニュース記事をタップした)
  • SYSTEM_APP_LAUNCHED (システムが、アプリケーションの起動を完了した)

「Reducer Action」の場合は、接頭辞を REDUCER で統一し、

  • REDUCER_VIEW_NEWS_FETCH_DONE (Sagaが、ニュースデータの取得を完了した)

といった感じにしています。

 

ほかにも、役割ごとにレイヤー化したデータフローの流れをガイドラインとして決めています。

スライド136

 

この流れに則っていないデータフローは禁止。禁止です。

これを守ることで、チームで分担して開発を進めていても、どこで何が発生しているか処理を追うことができるので、可読性や保守性が格段に向上します(したような気がします)。

 

まとめ

 

Saga を「副作用」ではなく、「主作用」として取り込むことで、非同期処理を含めたデータフローの単一方向性を維持できるようになったんじゃないかな、と個人的には思っております。

また、ユーザーの行動はほぼSagaを経由することになるので、GAなどのトラッキングも、Viewと分離した構造の中(Saga)に埋め込みやすいのではないかとも思います。

あとは、必ず Component → User Event → Container → User Action → Saga → Reducer Action → Reducer → State → Container → Component というデータフローで処理が行われるので、アプリケーションの挙動の見通しは良いのではないかと。

反面、デメリットも感じていて、React + Redux + Redux Saga 学習コストは最初がちょっと高いかもしれないなぁというのと、レイヤー構造やデータフローを常に 意識しながら設計する必要があるので、設計の難易度はちょっと高めかもしれません。

 

スライドでは、React Fiber と、FLUX Standard Action にもちょっと触れましたがブログ記事では割愛します。

 

それでは、より快適なフロントエンドライフを。

 

デワデワ。