web-dev-qa-db-fra.com

Charger dynamiquement les réducteurs redux avec le routeur de réaction 4

Je divise mon code en fonction des composants et je souhaite injecter mes réducteurs uniquement lorsqu'un composant est chargé, plutôt que de les empiler depuis le début dans le magasin. 

Dans le routeur de réaction 3, il était assez simple mais je n'arrive pas à le faire fonctionner avec le routeur de réaction 4.

Voici les réducteurs et le magasin:

réducteurs.js

import { combineReducers } from 'redux'
import { routerReducer } from 'react-router-redux'

import modalReducer from '../modules/modal'

export default combineReducers({
  routing : routerReducer,
  modal   : modalReducer
})

store.js

import { createStore, applyMiddleware, compose } from 'redux'
import { routerMiddleware } from 'react-router-redux'
import thunk from 'redux-thunk'
import createHistory from 'history/createBrowserHistory'
import rootReducer from './reducers'

export const history = createHistory()

const initialState = {}
const enhancers = []
const middleware = [
  thunk,
  routerMiddleware(history)
]

if (process.env.NODE_ENV === 'development') {
  const devToolsExtension = window.devToolsExtension

  if (typeof devToolsExtension === 'function') {
    enhancers.Push(devToolsExtension())
  }
}

const composedEnhancers = compose(
  applyMiddleware(...middleware),
  ...enhancers
)

const store = createStore(
  rootReducer(),
  initialState,
  composedEnhancers
)
export default store

Et j'utilise la charge paresseuse pour les itinéraires.

Comment puis-je mettre en œuvre des réducteurs divisés?

Je voudrais injecter les réducteurs asynchrones quelque chose comme ceci:

function createReducer(asyncReducers) {
  return combineReducers({
    ...asyncReducers,
    system,
    router,
  })
}

function injectReducer(store, { key, reducer }) {
  if (Reflect.has(store.asyncReducers, key)) return

  store.asyncReducers[key] = reducer
  store.replaceReducer(createReducer(store.asyncReducers))
}
12
S. Schenk

Dans react-router v4, pour l'injection asynchrone de réducteurs, procédez comme suit:

Dans votre fichier reducer.js, ajoutez une fonction appelée createReducer qui prend les injectedReducers sous forme d'arg et renvoie le réducteur combiné:

/**
 * Creates the main reducer with the dynamically injected ones
 */
export default function createReducer(injectedReducers) {
  return combineReducers({
    route: routeReducer,
    modal: modalReducer,
    ...injectedReducers,
  });
} 

Ensuite, dans votre fichier store.js

import createReducer from './reducers.js';

const store = createStore(
  createReducer(),
  initialState,
  composedEnhancers
);
store.injectedReducers = {}; // Reducer registry

Maintenant, afin d’injecter le réducteur de manière asynchrone lorsque votre conteneur à réaction monte, vous devez utiliser la fonction injectReducer.js de votre conteneur, puis composer tous les réducteurs avec connect . Exemple de composant Todo.js:

// example component 
import { connect } from 'react-redux';
import { compose } from 'redux';
import injectReducer from 'filepath/injectReducer';
import { addToDo, starToDo } from 'containers/Todo/reducer';

class Todo extends React.Component {
// your component code here
}
const withConnect = connect(mapStateToProps, mapDispatchToProps);

const addToDoReducer = injectReducer({
  key: 'todoList',
  reducer: addToDo,
});

const starToDoReducer = injectReducer({
  key: 'starredToDoList',
  reducer: starToDo,
});

export default compose(
  addToDoReducer,
  starToDoReducer,
  withConnect,
)(Todo);

React-Boilerplate est une excellente source pour comprendre toute cette configuration.Vous pouvez générer un exemple d’application en quelques secondes . Le code de injectReducer.js, configureStore.js (ou store.js dans votre cas) et En fait, toute cette configuration peut être prise à partir de react-boilerplate. Un lien spécifique peut être trouvé ici pour injectReducer.js , configureStore.js

11
Anu

Pour injecter les réducteurs de manière asynchrone, vous devez dans un premier temps écrire create store dans le format que vous avez mentionné:

Réducteurs

Dans les réducteurs, la seule différence consiste à obtenir asyncReducers en tant qu'entrée de la fonction createReducer et à l'utiliser de la manière suivante pour les réducteurs combinés.

function createReducer(asyncReducers) {
  return combineReducers({
    ...asyncReducers,
    system,
    router,
  })
}

Configurer le magasin

Votre fichier configureStore devrait ressembler à celui ci-dessous. J'ai apporté quelques modifications à votre structure. J'ai d'abord appliqué des middlewares dans les enhancers afin de pouvoir utiliser chrome redux DevTool Extention s'il est installé, sinon utiliser redux compos (et aussi utiliser reducer hot-reloader pour les réducteurs asynchrones).

import { createStore, applyMiddleware, compose } from 'redux'
import { routerMiddleware } from 'react-router-redux'
import thunk from 'redux-thunk'
import createHistory from 'history/createBrowserHistory'
import rootReducer from './reducers'

export const history = createHistory()

const initialState = {}

const middleware = [
  thunk,
  routerMiddleware(history)
]

const enhancers = [
  applyMiddleware(...middlewares),
];


/* eslint-disable no-underscore-dangle */
const composeEnhancers =
process.env.NODE_ENV !== 'production' &&
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
  ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
    // TODO Try to remove when `react-router-redux` is out of beta, LOCATION_CHANGE should not be fired more than once after hot reloading
    // Prevent recomputing reducers for `replaceReducer`
    shouldHotReload: false,
  })
  : compose;
/* eslint-enable */


const store = createStore(
   rootReducer(),
   initialState,
   composeEnhancers(...enhancers)
);

// Extensions
store.injectedReducers = {}; // Reducer registry

/ Make reducers hot reloadable, see http://mxs.is/googmo
/* istanbul ignore next */
if (module.hot) {
  module.hot.accept('./reducers', () => {
    store.replaceReducer(createReducer(store.injectedReducers));
  });
}

export default store;

Composant

Un composant simple sera comme ça. Comme vous le voyez dans ce composant, nous avons d'abord connect composant pour réagir-redux et pouvons utiliser mapStateToProps et mapDispatchToProps, puis pour injecter le réducteur de ce fichier, nous avons besoin de deux choses: 

1) le fichier réducteur, 2) injecter la fonction réducteur

ensuite, nous composons connect et reducerInjected au composant.

import React from 'react';
import { connect } from 'react-redux';
import { compose } from 'redux';
import reducerForThisComponent from './reducer';
import injectReducer from 'path_to_recuer_injector';

const Component = (props)=><div>Component</div>

function mapStateToProps (state){
   return {}
}
const withConnect = connect(mapStateToProps);
const withReducer = injectReducer({ key: 'login', reducerForThisComponent });

export default compose(
  withReducer,
  withConnect,
)(Component);

injectReducer.js

ce fichier peut être implémenté de différentes manières. Une des meilleures pratiques est mise en œuvre par react-boilerplate. C'est le fichier qui est utilisé pour injecter des réducteurs dans vos composants; Cependant, ce fichier a une autre dépendance (getInjectors.js) qui peut être placée dans un utilitaire à côté de injectReducer.js

import React from 'react';
import PropTypes from 'prop-types';
import hoistNonReactStatics from 'hoist-non-react-statics';

import getInjectors from './getInjectors';

/**
 * Dynamically injects a reducer
 *
 * @param {string} key A key of the reducer
 * @param {function} reducer A reducer that will be injected
 *
 */
export default ({ key, reducer }) => (WrappedComponent) => {
  class ReducerInjector extends React.Component {
    static WrappedComponent = WrappedComponent;
    static contextTypes = {
      store: PropTypes.object.isRequired,
    };
    static displayName = `withReducer(${(WrappedComponent.displayName || WrappedComponent.name || 'Component')})`;

    componentWillMount() {
      const { injectReducer } = this.injectors;

      injectReducer(key, reducer);
    }

    injectors = getInjectors(this.context.store);

    render() {
      return <WrappedComponent {...this.props} />;
    }
  }

  return hoistNonReactStatics(ReducerInjector, WrappedComponent);
};

getInjectors.js

import invariant from 'invariant';
import isEmpty from 'lodash/isEmpty';
import isFunction from 'lodash/isFunction';
import isObject from 'lodash/isObject';

import isString from 'lodash/isString';

import createReducer from '../reducers'; //The createStoreFile


/**
 * Validate the shape of redux store
 */
function checkStore(store) {
  const shape = {
    dispatch: isFunction,
    subscribe: isFunction,
    getState: isFunction,
    replaceReducer: isFunction,
    runSaga: isFunction,
    injectedReducers: isObject,
    injectedSagas: isObject,
  };
  invariant(
    conformsTo(store, shape),
    '(app/utils...) injectors: Expected a valid redux store'
  );
}


export function injectReducerFactory(store, isValid) {
  return function injectReducer(key, reducer) {
    if (!isValid) checkStore(store);

    invariant(
      isString(key) && !isEmpty(key) && isFunction(reducer),
      '(app/utils...) injectReducer: Expected `reducer` to be a reducer function'
    );

    // Check `store.injectedReducers[key] === reducer` for hot reloading when a key is the same but a reducer is different
    if (Reflect.has(store.injectedReducers, key) && store.injectedReducers[key] === reducer) return;

    store.injectedReducers[key] = reducer; // eslint-disable-line no-param-reassign
    store.replaceReducer(createReducer(store.injectedReducers));
  };
}

export default function getInjectors(store) {
  checkStore(store);

  return {
    injectReducer: injectReducerFactory(store, true),
  };
}

Maintenant que tout est défini, vous disposez de toutes les fonctionnalités telles que l'injection de réducteur et même la prise en charge de la charge de réducteur de module à chaud dans la phase de développement. Cependant, je suggère fortement deux choses:

  1. Il serait peut-être intéressant de consulter react-boilerplate car il offre de nombreuses fonctionnalités exceptionnelles mises en œuvre avec les meilleures pratiques axées sur les applications à grande échelle.

  2. Si vous envisagez de fractionner le code, cela signifie que vous allez avoir une application avec un problème d’évolutivité. Par conséquent, je recommande de ne pas utiliser redux-thunk et d'utiliser plutôt la saga redux. Et la meilleure solution consiste à Inject saga middlewares asynchronously et à éjecter les fichiers saga dès que le composant est démonté. Cette pratique peut améliorer votre application de plusieurs manières.

0
Mojtaba Izadmehr

Vous pouvez injecter non seulement des réducteurs, mais aussi des sagas, charger des pages par morceaux et rendre vos composants vraiment compétents avec leurs propres css et ressources (images, icônes). Il y a toute une philosophie à ce sujet - conception atomique , et voici un passe-partout qui poursuit une idée similaire:

https://github.com/react-boilerplate/react-boilerplate

Je me rends compte que ma réponse n’est pas assez complète, mais elle peut donner une idée plus précise des prochaines étapes.

0
sultan