Programing

Redux 애플리케이션에서 코드 분할을 위해 리듀서를 동적으로로드하는 방법은 무엇입니까?

crosscheck 2020. 5. 25. 21:04
반응형

Redux 애플리케이션에서 코드 분할을 위해 리듀서를 동적으로로드하는 방법은 무엇입니까?


Redux로 마이그레이션하겠습니다.

내 응용 프로그램은 많은 부분 (페이지, 구성 요소)으로 구성되어 있으므로 많은 감속기를 만들고 싶습니다. Redux 예제는 combineReducers()하나의 감속기를 생성 하는 사용해야한다는 것을 보여줍니다 .

또한 Redux 응용 프로그램에는 하나의 저장소가 있어야하며 응용 프로그램이 시작되면 만들어집니다. 상점을 만들 때 결합 감속기를 통과해야합니다. 응용 프로그램이 너무 크지 않은 경우 이치에 맞습니다.

그러나 둘 이상의 JavaScript 번들을 빌드하면 어떻게됩니까? 예를 들어, 각 응용 프로그램 페이지에는 고유 한 번들이 있습니다. 나는이 경우 하나의 결합 감속기가 좋지 않다고 생각합니다. 나는 Redux의 소스를 살펴보고 replaceReducer()기능 을 찾았습니다 . 내가 원하는 것 같습니다.

응용 프로그램의 각 부분에 대해 결합 된 감속기를 만들고 응용 프로그램의 부분 replaceReducer()사이를 이동할 때 사용할 수 있습니다.

이것이 좋은 접근법입니까?


업데이트 : 트위터가 어떻게하는지보십시오 .

이것은 정답은 아니지만 시작하는 데 도움이됩니다. 나는 오래된 감속기를 버리지 않고, 단지 새로운 감속기 를 조합 목록에 추가하고 있습니다. 이전 리듀서를 버릴 이유가 없습니다. 심지어 가장 큰 앱에서도 수천 개의 다이내믹 모듈이 없을 가능성이 높습니다.이 경우 애플리케이션에서 일부 리듀서를 분리 수 있습니다 .

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));
}

route.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);
      })
    }
  };

  // ...
}

이것을 표현하는 더 깔끔한 방법이있을 수 있습니다. 단지 아이디어를 보여주는 것입니다.


이것이 내가 현재 앱에서 구현 한 방법입니다 (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
  }
}

Create a registry instance when bootstrapping your app, passing in reducers which will be included in the entry bundle:

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

Then when configuring the store and routes, use a function which you can give the reducer registry to:

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

Where these functions look something like:

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
}

Here's a basic live example which was created with this setup, and its source:

It also covers the necessary configuration to enable hot reloading for all your reducers.


There is now a module that adds injecting reducers into the redux store. It is called Redux Injector.

Here is how to use it:

  1. Do not combine reducers. Instead put them in a (nested) object of functions as you would normally but without combining them.

  2. Use createInjectStore from redux-injector instead of createStore from redux.

  3. Inject new reducers with injectReducer.

Here is an example:

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);

Full Disclosure: I am the creator of the module.


As of October 2017:

  • Reedux

    implements what Dan suggested and nothing more, without touching your store, your project or your habits

There are other libraries too but they might have too many dependencies, less examples, complicated usage, are incompatible with some middlewares or require you to rewrite your state management. Copied from Reedux's intro page:


We released a new library that helps modulating a Redux app and allows dynamically adding/removing Reducers and middlewares.

Please take a look at https://github.com/Microsoft/redux-dynamic-modules

Modules provide the following benefits:

  • Modules can be easily re-used across the application, or between multiple similar applications.

  • Components declare the modules needed by them and redux-dynamic-modules ensures that the module is loaded for the component.

  • Modules can be added/removed from the store dynamically, ex. when a component mounts or when a user performs an action

Features

  • Group together reducers, middleware, and state into a single, re-usable module.
  • Add and remove modules from a Redux store at any time.
  • Use the included component to automatically add a module when a component is rendered
  • Extensions provide integration with popular libraries, including redux-saga and redux-observable

Example Scenarios

  • You don't want to load the code for all your reducers up front. Define a module for some reducers and use DynamicModuleLoader and a library like react-loadable to download and add your module at runtime.
  • You have some common reducers/middleware that need to be re-used in different areas of your application. Define a module and easily include it in those areas.
  • You have a mono-repo that contains multiple applications which share similar state. Create a package containing some modules and re-use them across your applications

Here is another example with code splitting and redux stores, pretty simple & elegant in my opinion. I think it may be quite useful for those who are looking for a working solution.

This store is a bit simplified it doesn't force you to have a namespace (reducer.name) in your state object, of course there may be a collision with names but you can control this by creating a naming convention for your reducers and it should be fine.

참고URL : https://stackoverflow.com/questions/32968016/how-to-dynamically-load-reducers-for-code-splitting-in-a-redux-application

반응형