Reduxアプリケーションでコード分割のためにレデューサーを動的にロードする方法は?


187

Reduxに移行します。

私のアプリケーションは多くのパーツ(ページ、コンポーネント)で構成されているため、多くのレデューサーを作成したいと考えています。Reduxの例は、combineReducers()1つのレデューサーを生成するために使用する必要があることを示しています。

また、私が理解しているように、Reduxアプリケーションは1つのストアを持つ必要があり、アプリケーションが起動すると作成されます。ストアが作成されているときに、結合したレデューサーを渡す必要があります。アプリケーションが大きすぎない場合、これは理にかなっています。

しかし、複数のJavaScriptバンドルをビルドするとどうなりますか?たとえば、アプリケーションの各ページには独自のバンドルがあります。この場合、結合された1つの減速機は良くないと思います。Reduxのソースを調べたところ、replaceReducer()機能が見つかりました。私が欲しいもののようです。

アプリケーションの各部分に結合されたレデューサーを作成し、アプリケーションのreplaceReducer()部分間を移動するときに使用できます。

これは良いアプローチですか?

回答:


245

更新:Twitterの仕組みもご覧ください。

これは完全な答えではありませんが、始めるのに役立ちます。私は古いレデューサーを捨てないことに注意してください—私は新しいものを組み合わせリストに追加するだけです。古いレデューサーを捨てる理由はないと思います。たとえ最大のアプリであっても、何千もの動的モジュールがある可能性は低いので、アプリケーションの一部のレデューサーを切断する必要があるかもしれません。

reducers.js

import { combineReducers } from 'redux';
import users from './reducers/users';
import posts from './reducers/posts';

export default function createReducer(asyncReducers) {
  return combineReducers({
    users,
    posts,
    ...asyncReducers
  });
}

store.js

import { createStore } from 'redux';
import createReducer from './reducers';

export default function configureStore(initialState) {
  const store = createStore(createReducer(), initialState);
  store.asyncReducers = {};
  return store;
}

export function injectAsyncReducer(store, name, asyncReducer) {
  store.asyncReducers[name] = asyncReducer;
  store.replaceReducer(createReducer(store.asyncReducers));
}

routes.js

import { injectAsyncReducer } from './store';

// Assuming React Router here but the principle is the same
// regardless of the library: make sure store is available
// when you want to require.ensure() your reducer so you can call
// injectAsyncReducer(store, name, reducer).

function createRoutes(store) {
  // ...

  const CommentsRoute = {
    // ...

    getComponents(location, callback) {
      require.ensure([
        './pages/Comments',
        './reducers/comments'
      ], function (require) {
        const Comments = require('./pages/Comments').default;
        const commentsReducer = require('./reducers/comments').default;

        injectAsyncReducer(store, 'comments', commentsReducer);
        callback(null, Comments);
      })
    }
  };

  // ...
}

これを表現するためのよりきちんとした方法があるかもしれません—私はただ考えを示しているだけです。


13
この種の機能がプロジェクトに追加されることを期待しています。コード分​​割や大規模なアプリケーションを処理する場合は、レデューサーを動的に追加する機能が必須です。一部のユーザーがアクセスできないサブツリー全体があり、すべてのリデューサーをロードすることは無駄です。redux-ignoreを使用しても、大きなアプリケーションはレデューサーを積み重ねることができます。
JeffBaumgardt

2
時には、重要でないものを「最適化」することは、より大きな無駄になります。
XML

1
うまくいけば、上記のコメントが理にかなっている...私が部屋を使い果たしたとき。これらは動的に異なる経路からロードされている場合でも、基本的に私は、私たちの状態ツリー上の単一の分岐にまとめレデューサーを持ってする簡単な方法が表示されない/homepage以上、その枝のがロードされた後、ユーザーが自分になったときにprofile.どのようにアン例これを行うには、素晴らしいでしょう。そうでなければ私は私の状態の木を平らに苦労を持っているか、私は非常に特定のブランチ名を持っている必要がありますuser-permissionsuser-personal
BryceHayden

1
また、初期状態にある場合はどうすればよいですか?
Stalsoは2016年

3
github.com/mxstbr/react-boilerplateボイラープレートは、ここで説明したものとまったく同じ手法を使用してレデューサーをロードします。
Pouya Sanooei

25

これは、現在のアプリに実装した方法です(GitHubの問題からのDanによるコードに基づいています!)

// Based on https://github.com/rackt/redux/issues/37#issue-85098222
class ReducerRegistry {
  constructor(initialReducers = {}) {
    this._reducers = {...initialReducers}
    this._emitChange = null
  }
  register(newReducers) {
    this._reducers = {...this._reducers, ...newReducers}
    if (this._emitChange != null) {
      this._emitChange(this.getReducers())
    }
  }
  getReducers() {
    return {...this._reducers}
  }
  setChangeListener(listener) {
    if (this._emitChange != null) {
      throw new Error('Can only set the listener for a ReducerRegistry once.')
    }
    this._emitChange = listener
  }
}

アプリをブートストラップするときにレジストリインスタンスを作成し、エントリバンドルに含まれるレデューサーを渡します。

// coreReducers is a {name: function} Object
var coreReducers = require('./reducers/core')
var reducerRegistry = new ReducerRegistry(coreReducers)

次に、ストアとルートを構成するときに、レデューサーレジストリに次の機能を提供できる関数を使用します。

var routes = createRoutes(reducerRegistry)
var store = createStore(reducerRegistry)

これらの関数は次のようになります。

function createRoutes(reducerRegistry) {
  return <Route path="/" component={App}>
    <Route path="core" component={Core}/>
    <Route path="async" getComponent={(location, cb) => {
      require.ensure([], require => {
        reducerRegistry.register({async: require('./reducers/async')})
        cb(null, require('./screens/Async'))
      })
    }}/>
  </Route>
}

function createStore(reducerRegistry) {
  var rootReducer = createReducer(reducerRegistry.getReducers())
  var store = createStore(rootReducer)

  reducerRegistry.setChangeListener((reducers) => {
    store.replaceReducer(createReducer(reducers))
  })

  return store
}

この設定で作成された基本的なライブの例とそのソースは次のとおりです。

また、すべてのレデューサーのホットリロードを有効にするために必要な構成についても説明します。


@jonnyに感謝します。ヘッドアップです。今の例ではエラーが発生しています。
Jason J. Nathan

createReducer()宣言があなたの回答にありません(私はそれがDan Abrahamovの回答にあることを知っていますが、これを含めると混乱を避けることができると思います)
パケットトレーサー2018年

6

reduxストアにレデューサーを挿入するモジュールが追加されました。これはRedux Injectorと呼ばれます。

使用方法は次のとおりです。

  1. 減速機を組み合わせないでください。代わりに、通常のように、それらを結合せずに、関数の(ネストされた)オブジェクトに配置します。

  2. reduxのcreateStoreの代わりにredux-injectorのcreateInjectStoreを使用します。

  3. injectReducerを使用して新しいリデューサーを挿入します。

次に例を示します。

import { createInjectStore, injectReducer } from 'redux-injector';

const reducersObject = {
   router: routerReducerFunction,
   data: {
     user: userReducerFunction,
     auth: {
       loggedIn: loggedInReducerFunction,
       loggedOut: loggedOutReducerFunction
     },
     info: infoReducerFunction
   }
 };

const initialState = {};

let store = createInjectStore(
  reducersObject,
  initialState
);

// Now you can inject reducers anywhere in the tree.
injectReducer('data.form', formReducerFunction);

完全な開示:私はモジュールの作成者です。


4

2017年10月現在:

  • Reedux

    店、プロジェクト、または習慣に触れることなく、Danが提案したものだけを実装します。

他にもライブラリがありますが、依存関係が多すぎたり、例が少なかったり、使用方法が複雑だったり、一部のミドルウェアと互換性がないか、状態管理を書き直す必要があります。Reeduxの紹介ページからコピー:


2

Reduxアプリの変調に役立ち、Reducerとミドルウェアを動的に追加/削除できる新しいライブラリをリリースしました。

見てください https://github.com/Microsoft/redux-dynamic-modulesを

モジュールには次の利点があります。

  • モジュールは、アプリケーション全体、または複数の類似したアプリケーション間で簡単に再利用できます。

  • コンポーネントは、それらが必要とするモジュールを宣言し、redux-dynamic-modulesは、モジュールがコンポーネントに確実にロードされるようにします。

  • モジュールはストアから動的に追加/削除できます。コンポーネントがマウントされたとき、またはユーザーがアクションを実行したとき

特徴

  • リデューサー、ミドルウェア、状態を1つの再利用可能なモジュールにグループ化します。
  • Reduxストアからモジュールをいつでも追加および削除できます。
  • 含まれているコンポーネントを使用して、コンポーネントのレンダリング時にモジュールを自動的に追加します
  • 拡張機能は、redux-sagaやredux-observableなどの一般的なライブラリとの統合を提供します

シナリオ例

  • すべてのレデューサーのコードを事前にロードする必要はありません。一部のレデューサーのモジュールを定義し、DynamicModuleLoaderとreact-loadableなどのライブラリを使用して、実行時にモジュールをダウンロードして追加します。
  • アプリケーションのさまざまな領域で再利用する必要がある一般的なレデューサー/ミドルウェアがあります。モジュールを定義し、それらの領域に簡単に組み込むことができます。
  • 同様の状態を共有する複数のアプリケーションを含むモノリポジトリがあります。いくつかのモジュールを含むパッケージを作成し、アプリケーション全体で再利用します

0

これはコード分​​割とreduxストアの別のですが、私の考えではかなりシンプルでエレガントです。私はそれが実用的な解決策を探している人にとって非常に役立つかもしれないと思います。

このストアは少し簡略化されているため、状態オブジェクトに名前空間(reducer.name)を設定する必要はありません。もちろん、名前との衝突が発生する可能性がありますが、レデューサーとその命名規則を作成することでこれを制御できます大丈夫です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.