1. saki-engineering

    Posted

    saki-engineering
Changes in title
+React Hooksで作るFlux入門
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,245 @@
+# この記事について
+モダンフロントエンドにおいて、Fluxというアプリケーションアーキテクチャが存在します。
+従来、Fluxの思想に従って実装を行うためには、同名ライブラリ"Flux"やReduxが採用されるケースが多かったのですが、React16.8でのReact Hooksの登場により、ライブラリに頼ることなくFluxを実現できるようになりました。
+本記事では、Fluxの概念・なぜそれが必要なのかについて説明した後、React Hooksを用いたFlux実装の一例を紹介します。
+
+## 使用する環境・バージョン
+- OS: macOS Mojave 10.14.5
+- Node.js: v12.13.0
+- npm: 6.13.4
+- React: 16.13.0
+
+## 読者に要求する前提知識
+JS, React, React Hooksが書けるだけの知識があること
+
+# Fluxとは?
+Fluxとは、UIをもつwebアプリケーションを構築するときのデザインパターン/アーキテクチャです。
+
+アプリケーションのデザインパターンといえば、他にはMVC(Model View Controller)パターンやMVVM(Model-View-ViewModel)などが存在します。
+Fluxもそれらと同様に、「アプリケーションを作るときに、どういう構造にするべきなのか」という考え方の一つなのです。
+
+## Fluxが誕生した背景
+MVCやMVVMがある中で、なぜFluxという思想が新しく生まれたのでしょうか。その疑問に答えるためには、背景を見ていきます。
+
+### レガシーアプリケーションの画面描画の仕組み
+従来のレガシーなWebアプリケーションというのは、以下のようなスタイルでコンテンツを生成していました。
+1. クライアントがHTTPリクエストを送信
+2. サーバーサイドで、リクエストに応じたHTMLを生成→送信(必要ならばAPI・DBなどからのデータ取得を行う)
+3. クライアントは、サーバーから受け取ったHTMLをそのまま表示
+
+このシステムでの特徴としては、「新しい画面・コンテンツの表示には、サーバーから新しく画面ファイルの取得→画面のリロードが必要」ということです。
+![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/31882022-997c-04a5-a843-9691d22fdc33.png)
+
+しかし、これではちょっとした画面更新だけで、HTMLやCSS,JSファイルをいちいちやりとりしなければいけないので、応答速度が落ちるという欠点がありました。
+
+### SPA(Single-Page Application)の画面描画の仕組み
+ここでSPA(Single-Page Application)というものが登場しました。これは、画面更新の際に
+1. クライアントがサーバーに画面更新に必要なデータを要求
+2. サーバーサイドは、画面のhtmlファイルではなく、要求されたデータのみをjson等で返す
+3. クライアントは、サーバーから受け取ったデータをもとに画面の一部を再レンダリング
+
+という風にして、画面更新時のクライアントーサーバー間のやりとりを軽量にし、パフォーマンスを向上させています。
+![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/6f26eb6b-cac1-a3d1-dd5f-80eb476206ae.png)
+
+SPAの登場によって、フロントエンドの役割が大きく転換することになります。
+レガシーアプリケーションでは、JSはサーバーサイドから受け取った画面のみを扱えばよかったのに対し、SPAでは画面更新時にどう正しく更新するかというところまでカバーしなくてはならないからです。
+
+(おまけ:ちなみに、ページ生成という仕事をフロント側に任せることができるようになったため、バックエンド側の役割も変化しています。フロントエンドがデータ取得に使うためだけのAPI・マイクロサービスが多く造られるようになり、それらがBFF(Backends For Frontends)と呼ばれるようになりました)
+
+参考:[今さら聞けない!シングルページアプリケーションとは](https://qiita.com/RinGoku/items/e44185d6103746b213c3)
+参考:[SPAにおける状態管理: 関数型のアプローチも取り入れるフロントエンド系アーキテクチャの変遷](https://employment.en-japan.com/engineerhub/entry/2019/05/23/103000)
+
+SPAの再レンダリング時に、正しく画面を更新するために必要な考え方が「状態管理」です。
+
+## SPAの状態管理について
+状態管理についてはこちらの記事が非常にわかりやすいため、これに沿って説明します。
+参考:[今から始めるReact入門 〜 flux編](https://qiita.com/TsutomuNakamura/items/cb3b6109fb21730cd73f)
+
+例えば、「未読1件」の表示を画面にしているときに、新たにサーバーサイドから追加の「未読2件」の通知がきたとします。
+このときフロントエンド側では、「今ある未読1件と、新たに追加された未読2件を合わせて、合計3件の未読がある」という風に判断し、表示しなければいけません。このように、「今の状態(=未読)」をずっと保持し、正しく更新し続ける機能のことを状態管理といいます。
+そのときに、状態管理がされていないと、サーバーサイドからきた未読「2件」という数字をそのまま表示することになってしまいます。
+
+## Fluxで状態管理を行う流れ
+Fluxはこの状態管理をReactでやりやすくしてくれます。
+
+Fluxは以下の4つの要素で構成されています。
+
+- View: フォームやボタンといった、アプリケーションの画面
+- Action: アプリケーションの更新情報を得る為の内部API
+- Dispatcher: Actionを受け取って、アプリケーションの更新作業を実際に行う関数
+- Store: アプリケーションの状態の保持を行うデータストア
+
+状態管理の流れとしては、
+1. Viewで(クリック等の操作を行って)どんな更新をしたいのか、Actionに通知
+2. ActionはViewからの通知を受け取って、dispatcherに渡す
+3. dispatcherは実際に状態を更新してviewに反映
+
+![flux-simple-f8-diagram-with-client-action-1300w.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/0b59077e-1b85-4831-8f36-53c90a4d2572.png)
+画像引用元:[Flux公式Doc In-Depth Overview](https://facebook.github.io/flux/docs/in-depth-overview/)
+
+このように、「Storeを更新するときは、必ずDispatcherを通す」という単方向のデータフローにすることで、Storeで保持されている状態や、それに伴い発生する画面遷移の把握が容易になります。
+参考:[Fluxとはなんなのか](https://qiita.com/knhr__/items/5fec7571dab80e2dcd92)
+
+# 実装
+ここからは、Fluxに沿った状態管理・画面更新をReact Hooksで実装していきます。
+
+今回は、「画面に複数個のボタンがあり、それぞれ押すとon/offが切り替わる&初期状態に戻すリセットボタンも別にある」というものを作ることを想定しています。
+
+## ディレクトリ構造
+Reactアプリのフォルダは、`create-react-app`コマンドで簡単に作成することができます。
+そのsrcディレクトリ以下で、今回関係があるところのみを抜粋して表示します。
+
+```
+src/
+ ├─ App.js
+ ├─ components
+ │ └─ Component.js
+ ├─ contexts
+ │ └─ Context.js
+ ├─ actions
+ │ └─ ActionCreater.js
+ └─ reducers
+ └─ Reducer.js
+```
+
+## Actionの実装
+Actionの実態はJSオブジェクトです。Actionは後々Dispatcherに渡されるものなので、このJSオブジェクトがDispatcher関数の引数となります。
+Actionオブジェクトは、「Storeに対してどんな操作がしたいのか」というのを`type`オブジェクトに保持していることが多いです。
+
+Actionの生成過程においては、生成actionをdispatchに渡すところまで実装することが多く、ここまでのメソッドをActionCreaterと呼びます。
+
+以下に、ActionCreaterの例を示します。
+
+```ActionCreater.js
+// buttonNoで指定された番号のボタンのon/offを切り替えるためのActionを発行→dispatcherに渡す
+export function toggleButton(dispatch, buttonNo) {
+ const action = {
+ type: "toggle",
+ data: {
+ button: buttonNo
+ }
+ };
+ dispatch(action);
+}
+
+// 全ボタンの状態を初期状態に戻すためのActionを発行→dispatcherに渡す
+export function resetAllButton(dispatch) {
+ const action = {
+ type: "reset",
+ };
+ dispatch(action);
+}
+```
+ここで利用している変数dispatchには、後述するdispatcher関数が格納されています。
+
+## Storeの実装
+Storeの実装は、Hooksの一つである`useReducer`の第一引数の`store`で行います。
+後述するdispatcherと一緒に、Hooksの一つである`useContext`を使ってApp内のどこでも使えるように共有する形になります。
+
+まずはContextを作ります。
+
+```Context.js
+import { createContext } from 'react'
+
+export const AppContext = createContext({
+ state: {
+ onState: Array(10).fill(false)
+ },
+ dispatch: null
+});
+```
+App内のどこでも呼び出せるようにしないといけないのが「StoreとDispatcher」なので、それぞれを保持するフィールド(`state`と`dispatch`)を用意しています。
+
+Contextを用意したら、今度はそれをApp全体で共有できるように`App.js`で設定を行います。
+
+```App.js
+import React, { useReducer } from 'react';
+import { AppContext } from './contexts/Context';
+import { AppReducer } from './reducers/Reducer';
+
+function App() {
+ const initialState = {
+ isState : Array(10).fill(false)
+ };
+
+ // initialStateで、state(≒store)の初期値を設定する
+ // ここで作ったdispatchには、state(≒store)を操作する関数が格納されている
+ const [state, dispatch] = useReducer(AppReducer, initialState);
+
+ return (
+ <div className="App">
+ // こうすることで、<AppContext>以下にある(略)部分のcomponentで
+ // 変数stateとdispatchを呼び出せるようになる
+ <AppContext.Provider value={{state, dispatch}}>
+ (略)
+ </AppContext.Provider>
+ </div>
+ );
+}
+
+export default App;
+```
+
+## Dispatcherの実装
+dispatcherはStoreの変更・更新を行うものです。
+これの実態は`useReducer`の第二返り値の`dispatch`関数です。これの実装は第一引数`AppReducer`の中で行います。
+つまり、言い方を変えれば「`useReducer`の第一引数で渡された関数が、第二返り値の`dispatch`に格納される」のです。
+
+Reducerは、現在のStateと新たに生成されたActionを引数として受け取り、新しいStateを返り値として返す関数です。
+
+```js
+(nowState, action) => newState
+```
+dispatcherの実装を担う`useReducer`の第一引数「`AppReducer`」を、この条件に合うよう、引数を「現在のState, Action」、返り値を「新しいState」として作ります。
+
+
+```Reducer.js
+export function AppReducer(state, action) {
+ var NewonState = state.onState.slice();
+
+ // actionのtypeによって、newStateの生成処理を変える
+ switch(action.type){
+ case 'toggle':
+ var i = action.data.button;
+ NewisPlayed[i] = !state.onState[i];
+ return {onState: NewonState}
+ case 'reset':
+ var filledfalse = Array(10).fill(false);
+ return {onState: filledfalse}
+ default:
+ return state;
+ }
+}
+```
+
+## ViewからのAction発行
+ActionCreater, dispatcher, storeが用意できたら、いよいよViewからActionを発行してStoreを変更するロジックです。
+Viewでやらなければいけないのは、以下2つです。
+
+- ActionCreaterの呼び出し
+- ActionCreaterに引数として渡すdispatcher関数の用意
+
+例えば、「ボタンをクリックしたら、ActionCraterのtoggleButtonを呼ぶ」ためには以下のように記述します。
+
+```Component.js
+import React, { useContext } from 'react';
+// ActionCreaterのインポート
+import { toggleButton } from '../actions/actionCreaters';
+
+function MyButton(props) {
+ // Contextにあるdispatcher関数を取得
+ const {dispatch} = useContext(AppContext);
+
+ return (
+ // 使いたい場所でActionCreater関数を呼び出す
+ <button onClick={() => toggleButton(dispatch, props.buttonno)}>
+ {props.buttonno}
+ </button>
+ );
+}
+```
+
+## ソースコードの参考文献
+
+- [FluxによるReactアプリの状態管理 ReactHooks編](https://qiita.com/koedamon/items/13df746dd82f760a0247)
+- [React HooksとContextAPIでFluxをやってみる](https://tech.connehito.com/entry/react-hooks-with-flux)