メインコンテンツへスキップ

設計上の決定事項

非公式ベータ版翻訳

このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →

Redux FAQ: 設計上の決定事項

なぜReduxはstateとactionをsubscriberに渡さないのか?

Subscriberはアクションではなく、stateの値自体に応答するように設計されています。stateの更新は同期的に処理されますが、subscriberへの通知はバッチ処理されたりデバウンスされたりするため、すべてのアクションごとに通知されるわけではありません。これは再レンダリングの繰り返しを避ける一般的なパフォーマンス最適化手法です。

バッチ処理やデバウンスは、enhancerを使ってstore.dispatchをオーバーライドし、subscriberが通知される方法を変更することで実現できます。また、パフォーマンス最適化と再レンダリング抑制のために、アクションをバッチ処理するようReduxを変更するライブラリも存在します:

  • redux-batch はアクションの配列をstore.dispatch()に渡し、通知を1回だけにできます

  • redux-batched-subscribe はdispatchの結果発生するsubscribe通知をバッチ処理します

Reduxが保証するのは、最終的にすべてのsubscriberが最新のstateで呼び出されることであり、すべてのアクションに対して各subscriberが呼び出されることではありません。storeのstateはsubscriber内でstore.getState()を呼び出すだけで取得できます。アクションをsubscriberで利用可能にすると、アクションのバッチ処理が破綻するため実現不可能です。

サポートされていない機能ですが、subscriber内でアクションを使用する潜在的なユースケースとして、特定の種類のアクション後にのみコンポーネントを再レンダリングする制御が考えられます。代わりに再レンダリングは以下の方法で制御すべきです:

  1. shouldComponentUpdate ライフサイクルメソッド

  2. 仮想DOM等価性チェック(vDOMEq)

  3. React.PureComponent

  4. React-Reduxの使用: mapStateToProps でコンポーネントが必要なstoreの部分のみをsubscribe

参考情報

記事

ディスカッション

なぜReduxはアクションやリデューサーにクラスを使用することをサポートしないのか?

オブジェクト指向プログラミングの経験が豊富な開発者にとって、アクションオブジェクトを返す関数(アクションクリエイター)を使用するパターンは直感的ではないかもしれません。しかしアクションオブジェクトやリデューサーにクラスインスタンスを使用することは、シリアライズ/デシリアライズを複雑にするためサポートされていません。JSON.parse(string)のようなデシリアライズメソッドはクラスインスタンスではなくプレーンなJavaScriptオブジェクトを返します。

storeに関するFAQで説明されている通り、永続化やタイムトラベルデバッグが正しく機能しなくても問題ない場合、非シリアライズ可能なアイテムをReduxストアに格納することは可能です。

シリアライズにより、ブラウザはディスパッチされた全アクションと過去のstore状態を少ないメモリで保存できます。状態の巻き戻しや「ホットリロード」はRedux開発者体験の中核であり、Redux DevToolsの機能でもあります。これによりサーバーサイドレンダリング時にも、デシリアライズされたアクションをサーバーに保存し、ブラウザで再シリアライズすることが可能になります。

参考情報

記事

ディスカッション

なぜミドルウェアのシグネチャはカリー化を使用するのですか?

Reduxミドルウェアは、const middleware = (storeAPI, next, action) => {}のような単一関数ではなく、const middleware = storeAPI => next => action => {}という三重にネストされた関数構造で記述されます。これにはいくつかの理由があります。

第一に、関数の「カリー化」は関数型プログラミングの標準的な技法であり、Reduxは設計時に明示的に関数型プログラミングの原則を採用することを意図していました。第二に、カリー化された関数はクロージャを生成し、ミドルウェアのライフサイクル全体で存続する変数を宣言できます(これはクラスインスタンスの存続期間に存在するインスタンス変数の関数型版と見なせます)。最後に、これは単にReduxが最初に設計された時に選択されたアプローチだったからです。

ミドルウェア宣言のカリー化された関数シグネチャは、applyMiddleware関数実行時にstoreとnextの両方が利用可能なため不要と考える人もいます。しかし、既存のミドルウェア定義に依存する何百ものライブラリが存在するため、この問題は破壊的変更を導入する価値がないと判断されました。

参考情報

ディスカッション

なぜapplyMiddlewaredispatchにクロージャを使用するのですか?

applyMiddlewareはストアから既存のdispatchを取得し、getState関数とdispatch関数を公開するオブジェクトで呼び出されるミドルウェアの初期チェーンを作成するためにクロージャで包みます。これにより、初期化中にdispatchに依存するミドルウェアの実行が可能になります。

参考情報

ディスカッション

  • なぜapplyMiddlewareはdispatchにクロージャを使用するのか?

なぜcombineReducersは各リデューサー呼び出し時に状態全体を第三引数として渡さないのですか?

combineReducersはドメイン単位でのリデューサーロジック分割を促進するように設計されています。Beyond combineReducersで述べている通り、combineReducersは意図的に単一の一般的なユースケース(プレーンJavaScriptオブジェクトの状態ツリー更新を、各状態スライスの更新作業を特定のスライスリデューサーに委譲する形で処理)に限定されています。

各リデューサーへの第三引数として何を渡すべきか(状態ツリー全体、コールバック関数、状態の別部分など)は自明ではありません。combineReducersがユースケースに合わない場合、深くネストされたリデューサーやグローバル状態へのアクセスが必要なケースでは、combineSectionReducersreduceReducersなどの代替ライブラリの使用を検討してください。

公開されているユーティリティでニーズを満たせない場合、必要な処理を正確に行う独自関数を作成することも常に可能です。

参考情報

記事

ディスカッション

なぜmapDispatchToPropsgetState()mapStateToProps()の戻り値の使用を許可しないのか?

mapDispatch内でstate全体やmapStateの戻り値を使用したいという要望がありました。これによりmapDispatch内で宣言される関数がストアの最新値をクロージャで捕捉できるようになるためです。

このアプローチはmapDispatchでサポートされていません。ストア更新のたびにmapDispatchも呼び出されることになり、状態更新ごとに関数が再生成されてパフォーマンスに重大なオーバーヘッドが生じるためです。

このユースケース(現在の状態とmapDispatchToProps関数に基づいてpropsを変更する必要がある場合)に対処する推奨方法は、connect関数の第三引数であるmergePropsを使用することです。指定すると、mapStateToProps()の結果、mapDispatchToProps()の結果、コンテナコンポーネントのpropsが渡されます。mergePropsから返されるプレーンオブジェクトがラップされたコンポーネントにpropsとして渡されます。

参考情報

ディスカッション