Como carregar dinamicamente redutores para divisão de código em um aplicativo Redux?

187

Vou migrar para o Redux.

Meu aplicativo consiste em várias partes (páginas, componentes), por isso quero criar muitos redutores. Exemplos de redux mostram que eu deveria usar combineReducers()para gerar um redutor.

Além disso, como eu entendo, o aplicativo Redux deve ter um armazenamento e é criado assim que o aplicativo é iniciado. Quando a loja estiver sendo criada, devo passar meu redutor combinado. Isso faz sentido se o aplicativo não for muito grande.

Mas e se eu criar mais de um pacote JavaScript? Por exemplo, cada página do aplicativo possui seu próprio pacote. Eu acho que, neste caso, o redutor combinado não é bom. Procurei nas fontes do Redux e encontrei replaceReducer()funções. Parece ser o que eu quero.

Eu poderia criar um redutor combinado para cada parte do meu aplicativo e usá-lo replaceReducer()quando me mover entre partes do aplicativo.

Será esta uma boa abordagem?

Pavel Teterin
fonte

Respostas:

245

Atualização: veja também como o Twitter faz isso .

Esta não é uma resposta completa, mas deve ajudá-lo a começar. Observe que não estou jogando fora redutores antigos - apenas adicionando novos à lista de combinações. Não vejo motivo para jogar fora os redutores antigos - mesmo no aplicativo maior, é improvável que você tenha milhares de módulos dinâmicos, que é o ponto em que você pode desconectar alguns redutores do aplicativo.

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

  // ...
}

Pode haver uma maneira mais clara de expressar isso - estou apenas mostrando a ideia.

Dan Abramov
fonte
13
Eu adoraria ver esse tipo de funcionalidade adicionada ao projeto. A capacidade de adicionar redutores dinamicamente é essencial ao lidar com a divisão de código e aplicativos grandes. Eu tenho subárvores inteiras que podem não ser acessadas por alguns usuários e carregar todos os redutores é um desperdício. Mesmo com redux ignore grandes aplicativos podem realmente empilhar redutores.
precisa saber é o seguinte
2
Às vezes, é um desperdício maior 'otimizar' algo sem importância.
XML
1
Espero que o comentário acima faça sentido ... quando eu fiquei sem espaço. Mas, basicamente, não vejo uma maneira fácil de combinar os redutores em um único ramo em nossa árvore de estado, quando eles são carregados dinamicamente de rotas diferentes /homepagee, em seguida, mais desse ramo é carregado quando o usuário acessa profile.um exemplo. fazer isso, seria incrível. Caso contrário, eu tenho um tempo difícil achatando minha árvore do estado ou eu tenho que têm nomes de filiais muito específicas user-permissionseuser-personal
BryceHayden
1
E como devo agir, se tiver estado inicial?
Stalso
3
O github.com/mxstbr/react-boilerplate usa a mesma técnica mencionada aqui para carregar redutores.
Pouya Sanooei 15/08/16
25

É assim que eu o implementei em um aplicativo atual (com base no código de Dan de um problema do GitHub!)

// 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
  }
}

Crie uma instância do registro ao inicializar seu aplicativo, passando redutores que serão incluídos no pacote de entrada:

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

Em seguida, ao configurar o armazenamento e as rotas, use uma função à qual você pode atribuir o registro do redutor:

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

Onde essas funções se parecem com:

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
}

Aqui está um exemplo básico ao vivo, criado com essa configuração e sua fonte:

Ele também cobre a configuração necessária para permitir o recarregamento a quente de todos os seus redutores.

Jonny Buchanan
fonte
Obrigado @ Jonny, apenas um aviso, o exemplo está lançando um erro agora.
Jason J. Nathan
createReducer declaração () está faltando a sua resposta (eu sei que é em resposta de Dan Abrahamov mas acho que inclusive seria evitar confusão)
Packet Tracer
6

Agora existe um módulo que adiciona redutores de injeção no repositório redux. É chamado Redux Injector .

Aqui está como usá-lo:

  1. Não combine redutores. Em vez disso, coloque-os em um objeto (aninhado) de funções como faria normalmente, mas sem combiná-los.

  2. Use createInjectStore do redux-injector em vez de createStore do redux.

  3. Injete novos redutores com injectReducer.

Aqui está um exemplo:

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

Divulgação completa: Eu sou o criador do módulo.

Randall Knutson
fonte
4

Em outubro de 2017:

  • Reedux

    implementa o que Dan sugeriu e nada mais, sem tocar sua loja, seu projeto ou seus hábitos

Também existem outras bibliotecas, mas elas podem ter muitas dependências, menos exemplos, uso complicado, são incompatíveis com alguns middlewares ou exigem que você reescreva seu gerenciamento de estado. Copiado da página de introdução do Reedux:

Silviu-Marian
fonte
2

Lançamos uma nova biblioteca que ajuda a modular um aplicativo Redux e permite adicionar / remover dinamicamente redutores e middlewares.

Consulte https://github.com/Microsoft/redux-dynamic-modules

Os módulos oferecem os seguintes benefícios:

  • Os módulos podem ser facilmente reutilizados no aplicativo ou entre vários aplicativos similares.

  • Os componentes declaram os módulos necessários por eles e redux-dynamic-modules garante que o módulo seja carregado para o componente.

  • Os módulos podem ser adicionados / removidos da loja dinamicamente, ex. quando um componente é montado ou quando um usuário executa uma ação

Recursos

  • Agrupe redutores, middleware e estado em um único módulo reutilizável.
  • Adicione e remova módulos de uma loja Redux a qualquer momento.
  • Use o componente incluído para adicionar automaticamente um módulo quando um componente for renderizado
  • As extensões fornecem integração com bibliotecas populares, incluindo redux-saga e redux-observable

Cenários de exemplo

  • Você não deseja carregar o código de todos os seus redutores antecipadamente. Defina um módulo para alguns redutores e use DynamicModuleLoader e uma biblioteca como react-loadable para baixar e adicionar seu módulo em tempo de execução.
  • Você tem alguns redutores / middlewares comuns que precisam ser reutilizados em diferentes áreas do seu aplicativo. Defina um módulo e inclua-o facilmente nessas áreas.
  • Você tem um repo-mono que contém vários aplicativos que compartilham um estado semelhante. Crie um pacote contendo alguns módulos e reutilize-os em seus aplicativos
Code Ninja
fonte
0

Aqui está outro exemplo com divisão de código e lojas redux, bastante simples e elegante na minha opinião. Eu acho que pode ser bastante útil para quem está procurando uma solução funcional.

Esta loja é um pouco simplificada, não força você a ter um espaço para nome (reducer.name) em seu objeto de estado, é claro que pode haver uma colisão com nomes, mas você pode controlar isso criando uma convenção de nomenclatura para seus redutores e deve ficar bem.

Maksym Oliinyk
fonte