JavaScript: Reduxが必要なとき/不要なとき(翻訳)

概要 原著者の許諾を得て翻訳・公開いたします。 英語記事: When (and when not) to use Redux – LogRocket 原文公開日: 2018/01/20 著者: Christian Nwamba サイト: LogRocket 図は英語記事からの引用です。 JavaScript: Reduxが必要なとき/不要なとき(翻訳) Reduxが登場するまで、複雑なタスクを組むときのステート管理は相当つらい作業でした。Reduxは、Fluxアプリのデザインパターンにヒントを得て、JavaScriptアプリでステートを管理するために設計されました。ReduxはReactと併用されることが多いのですが、ReduxはjQueryやAngular、Vueといった別のフレームワークと併用することもできます。 Reduxのサイズは非常に小さい(依存関係も含めてわずか2KB)にもかかわらず、アプリの各コンポーネントが自分のステートに直接アクセスできるようになります。このとき、子コンポーネントにpropsを送信する必要も、親コンポーネントがデータを受け取るのにコールバック関数を使う必要もありません。 本記事では、Reduxがいかに関数型プログラミングに深く根ざしているか、そしてアプリにReduxを導入するかどうかをうまく決定する方法について解説したいと思います。 「どうしてReduxが必要なの?」 ピカピカの新しいツールが登場するたびにいきなり飛びついてプロジェクトに導入したりしないのは今や常識です。結局のところ、あらゆるコンポーネントはステートを持たないのでしょうか?ステート管理ツールがなぜ今必要なのでしょうか? 誤解しないでいただきたいのですが、Reactはそれだけで十分素晴らしいフレームワークであり、他に何もなくても完全なアプリを書けます。しかしアプリが複雑になるに連れてコンポーネントの数も増加し、フレームワークだけでこれらを管理するのは非常に難しくなることがあります。 そこに登場するのが複雑なアプリを扱いやすくしてくれるReduxです。Reactを少し使ってみれば、Reactのデータフローが親コンポーネントから子コンポーネントにpropsを渡す作業であることがわかります。多数のコンポーネント間をステートやpropsを介してデータフローが行き交う巨大なアプリ内ではコミュニケーションエラーが発生しやすくなり、(信じていただきたいのですが)コードはいつしか読むのも改善するのも非常に困難になってしまいます。 理解のため、以下の図をご覧ください。 https://css-tricks.com/learning-react-redux/より Reactでは(他のフレームワークでも同様ですが)、親子関係にないコンポーネント同士でのやりとりがスムーズにできません。Reactでは、それが必要な場合にはFluxのパターンに従ってグローバルなイベントシステムを構築せよとアドバイスしています。そしてここがReduxの出番なのです。 Reduxを使うと、アプリの全ステートを保持する「ストア」が使えるようになります。コンポーネントAでステートが変更されるとそのことがストアに伝わります。コンポーネントAのステート変更を監視する必要のあるコンポーネントBやCはストアにサブスクライブすることで、ステートの変更がストアから中継されます。 https://css-tricks.com/learning-react-redux/より おわかりいただけましたでしょうか?思ったよりずっとよくできています。コンポーネント間のコミュニケーションを元のまま放置していれば、やがてエラーが生じやすくなり、とても読んでいられないコードベースになってしまいます。Reduxはこの流れを変えます。 コンポーネントAが自分のステート変更をストアに送信すると、コンポーネントBとCがステートの変更を必要としている場合に、BとCがストアからAの変更を取得できます。このようにして、データフローのロジックがシームレスになります。 Reduxの本来の目的以外にも、Reduxを使うメリットはたくさんあります。中でも私が重要だと思う部分をリストアップします。 1. 結果が予測可能 「信頼できる情報源」(ストア)が1つだけに限定されているので、アクションとアプリの他の部分の現在のステートを同期するときに伴う問題がほとんど生じません。 2. メンテナンスしやすい Reduxではコードの編成方法に関する厳密なガイドラインが定められています。これにより結果がさらに予測しやすくなり、コードのメンテナンスも容易になります。 3. テストしやすい Reduxで書くコードは、テストしやすいコードを書くための黄金律に従って分離される純粋な関数になります。1つの作業だけを行う、独立した小さい関数を書くようにしましょう。 「Reduxって結局要らなくね?」 この点については既に自明な方もいらっしゃるかと思いますが、一応説明いたします。Reduxを無理して使う必要はありません。Reduxを使わない理由が今ひとつ見えにくいこともあるかもしれませんが、以下のいずれかに該当する場合は、おそらくReduxは完全に不要でしょう。 自分や友だち(そこまで仲良くなければ同僚でも構いませんが)が、コンポーネント間でステートの共有や加工を行う方法を既に定義済みの場合 Reactなどのフレームワーク経験がまだ浅い場合 アプリのほとんどの動作で今後もシンプルなUI変更などぐらいしか行う予定がなく、Reduxストアに含める必要もなければコンポーネントレベルで扱えるようにする必要もない場合 サーバーサイドイベント(SSE)やWebSocketsを管理する必要がない場合 ビューごとにフェッチするデータソースが1つしかない場合 Reduxの構成要素 初心者がやり方に戸惑わないよう、Reduxのライブラリは2KBに抑えられ、ツール自体が「アクション」「ストア」「レデューサー」の3つのパーツで構成されています。 https://stackoverflow.com/questions/45416237/axios-calls-in-actions-reduxより アクション(action) アクションは、関数を用いて生成されるシンプルなイベントであり、アプリのデータをストアに送信します。データの送信は、フォームの送信/API呼び出し/基本ユーザー操作などさまざまな方法で行われる可能性があります。Reduxのあらゆるアクションにはtypeプロパティがあり、アクションの種類(type)や、ストアに送信される情報の「ペイロード」を記述します。アクションの最も基本的な動作例をご覧ください(Gist)。 // The action { type: ADD_USER, payload: { username: ‘Chris’, email: ‘redux @frameworks.io’ } } // The function that creates the action function signUpUser(data) { return { type: ADD_USER, payload: data } } Reduxには、アプリのどこからでもアクションを呼べるようにするためのdispatch()メソッドが用意されています。このメソッドはアクションをReduxストアに送信してステートの変更を表明します(Gist)。 dispatch(signUpUser(data)); レデューサー(reducer) Reduxではステートをアプリから(直接)変更できないようになっています。変更にはdispatch()を使います。dispatch()はステート変更の意図を表明するだけのメソッドであり、実際の変更はReduxが行います。 レデューサーは関数であり、ディスパッチされたアクションを介してアプリの現在のステートを受け取り、新しいステートを返します。以下のレデューサーが、現在のステートとアクションを引数として受け取り、次のステートを返す様子をご覧ください(Gist)。 function handleAuth(state, action) { return _.assign({}, state, { auth: action.payload }); } もっと複雑なアプリを作る場合は、ReduxのcombineReducers()メソッドの利用が推奨されます。このメソッドはアプリ内のすべてのレデューサーを1つのレデューサーリストにまとめます。あらゆるレデューサーはここでアプリのステートのパーツを扱い、ステートのパラメータはレデューサーごとに異なります(Gist)。 const indexReducer = combineReducers({ signUp: signUp, editProfile: editProfile, makePayment: makePayment }); ここでもうひとつ注意しておきたいのは、レデューサーは「純粋関数」で記述すべきであるという点です。純粋関数の特徴を以下にリストアップしました。 外部ネットワークや外部データベースへの呼び出しを行わないこと 戻り値は、パラメータの値だけに応じて変わること 引数はイミュータブルに見えること(変更されるべきでないという意味) ストア(store) ストアはReduxの心臓部に相当します。ストアは、アプリのあらゆるステートを保持する単一の信頼できる情報源であり、ステートへのアクセス/アクションのディスパッチ/リスナーの登録を行うメソッドを提供します。ディスパッチされたアクションは、レデューサを介して常に新しいステートをストアに保存します。Reduxストアの基本的な例をご覧ください(Gist)。 import { createStore } from‘ redux’; let store = createStore(indexReducer); let signUpInfo = { username: ‘Chris’, email: ‘redux @frameworks.io’ }; store.dispatch(signUpUser(signUpInfo)); 関数型プログラミングとRedux Reduxを使うことになったら、関数型プログラミングの動作について知っておくべきです。Reduxは関数型プログラミングの原理に基づいて作られているので、関数型プログラミングのコンセプトを理解しておくことでReduxがどのようにして操作を行っているかについて洞察を得ることができます。 関数型プログラミングの重要な点についてガイドラインをざっと追ってみましょう。 純粋で、再帰可能で、高階のクロージャや無名関数を利用できる map/filter/reduceなどのヘルパー関数を利用できる 関数は互いにチェイン(連鎖)できる 関数は第一級オブジェクトとして扱える 関数は引数として渡せる 関数/再帰/配列を用いてフローを制御できる 関数のステートは変更されない(イミュータブル) コードの実行順序は重要ではない 関数型プログラミングでは、シンプルで小さく分離された関数を書きます。このパターンに従うことでコードのメンテナンスやテストやデバッグがやりやすくなります。関数は小さく分離されているので再利用しやすくなり、必要に応じてどこにでもコピペできます。 これは私の意見ですが、関数型プログラミングでは必要なコード量が少なくて済みます。関数型プログラミングを使うときは、先に触れた純粋関数、無名関数、クロージャ、高階関数のコンセプトを理解しておくことが重要です。 まとめ Reduxはアプリのステート管理のための素晴らしいライブラリであり、Reduxが多くの開発者を魅了していることは確かです。それ以外に何を知る必要があるでしょうか? ReduxはUberやTwitterなどの企業で大規模に用いられているだけではなく、WordPressなどのプロジェクトでも実装に成功しています。Reduxはどんな既存アプリにも使える万能ライブラリではないという論争が起きていることも確かですし、そのとおりです。 シンプルなアクションだけを用いるアプリやサーバーサイドレンダリングの不要なアプリでは、おそらくReduxは不要でしょう。Reduxはアクションをコンポーネントレベルで扱いますそうしたアプリのアクションはコンポーネントレベルで扱えます。 いずれにしろReduxの素晴らしさに変わりはありません。特にReactをお使いの方ならぜひReactReduxをチェックすべきでしょう。 お知らせ: Webアプリの「監視カメラ」とも言うべきLogRocketについて LogRocketは、ブラウザで起きた問題を再生できるフロントエンドログ出力ツールです。エラーの原因をあれこれ推測したり、ユーザーからスクショやログダンプを送ってもらったりしなくても、LogRocketでセッションを再生して素早く原因を理解できるようになります。LogRocketはフレームワークの種類を問わずあらゆるアプリで利用可能であり、プラグインを使えばReduxやVueや@ngrx/storeの追加コンテキストをログ出力することもできます。 LogRocketは、Reduxのアクションやステートの他にも、コンソールログやJavaScriptエラー、スタックトレース、ネットワークリクエスト/レスポンス(bodyとブラウザメタデータを含む)、カスタムログも記録できます。DOMのインスツルメンテーション機能でページのHTMLやCSSを記録することも、どんなに複雑なSPA(シングルページアプリ)でもピクセル単位まで完璧な動画に再作成することもできます。 関連記事 Rails: JSON Patchでパフォーマンスを向上(翻訳) WebサイトをPWA(Progressive Web App)に変える簡単な手順(翻訳) JavaScript: Parcel.jsでReact.jsプロジェクトを作成する(翻訳)