Redux: Uma preocupação no combineReducer, quando uma ação está relacionada a vários redutores

Criado em 21 ago. 2015  ·  52Comentários  ·  Fonte: reduxjs/redux

Estou trabalhando em um aplicativo de chat criado com React.js e Flux. Ao ler a documentação do Redux, a função combineReducer parece estranha para mim. Por exemplo:

Existem três lojas chamadas messageStore , unreadStore , threadStore . E há uma ação chamada NEW_MESSAGE . Todas as três lojas serão atualizadas com novas mensagens. No Redux, as lojas são redutoras combinadas com combineReducer como:

message = (state=[], action) ->
  switch action.type
    when NEW_MESSAGE
      state # new state
unread = (state=[], action) ->
  switch action.type
    when NEW_MESSAGE
      state # new state
thread = (state=[], action) ->
  switch action.type
    when NEW_MESSAGE
      state # new state

combineReducer {message, unread, thread}

Parece-me que, nesses casos, dividir os redutores não está tornando minha vida mais fácil. No entanto, uma loja gigante construída com js imutáveis ​​talvez seja uma solução melhor para isso. Gostar:

initialState = Immutable.fromJS
  message: []
  unread: []
  thread: []

allStore = (store=initialState, action) ->
  switch action.type
    when NEW_MESSAGE
        initialState
        .updateIn ['message'], (messages) -> messages # updated
        .updateIn ['urnead'], (urneads) -> unreads # updated
        .updateIn ['thread'], (threads) -> threads # updated
question

Comentários muito úteis

para tal ação, tenho que mantê-lo em vários redutores (ou arquivos depois que meu aplicativo crescer). Em nossa base de código atual, já vimos 5 lojas lidando com a mesma ação, é um pouco difícil de manter.

Se isso é bom ou ruim, depende do seu aplicativo. Na minha experiência, ter muitas lojas (ou redutores Redux) lidando com a mesma ação é realmente muito conveniente porque divide responsabilidades e também permite que as pessoas trabalhem em ramos de recursos sem colidir com o resto da equipe. Na minha experiência, é mais fácil manter mutações não relacionadas separadamente do que uma mutação gigante.

Mas você está certo, há casos em que isso não funciona muito bem. Eu diria que muitas vezes são sintomas de um modelo de estado subótimo. Por exemplo,

em alguns casos, um redutor pode depender de dados de outro (como mover uma mensagem de não lida para mensagem ou até mesmo promover uma mensagem como um novo tópico de mensagem para tópico)

é um sintoma de um problema. Se você tiver que mover muito as coisas dentro do seu estado, talvez a forma do estado precise ser mais normalizada. Em vez de { readMessages, unreadMessages } você pode ter { messagesById, threadsById, messageIdsByThreadIds } . Mensagem lida? Tudo bem, alterne state.messagesById[id].isRead . Quer ler mensagens para um tópico? Pegue state.threadsById[threadId].messageIds , depois messageIds.map(id => state.messagesById[id]) .

Quando os dados são normalizados, você realmente não precisa copiar itens de um array para outro. Na maioria das vezes, você estaria invertendo sinalizadores ou adicionando / removendo / associando / desassociando IDs.

Todos 52 comentários

Você pode explicar por que você acha que combineReducers não é útil neste caso? De fato, para o estado semelhante a um banco de dados, você geralmente deseja um único redutor (e, em seguida, outros redutores para o estado da IU, por exemplo, item selecionado, etc).

Veja também:

https://github.com/rackt/redux/tree/master/examples/real-world/reducers
https://github.com/rackt/redux/blob/master/examples/async/reducers

para inspiração.

O primeiro exemplo que você apontou é semelhante ao meu caso, requestType é tratado em ambos os redutores.
https://github.com/rackt/redux/blob/master/examples/real-world/reducers/paginate.js#L28

A divisão de redutores facilita a manutenção quando dois redutores são separados de outros redutores. Mas quando eles estão realizando as mesmas ações, isso leva a:

  • para tal ação, tenho que mantê-lo em vários redutores (ou arquivos depois que meu aplicativo crescer). Em nossa base de código atual, já vimos 5 lojas lidando com a mesma ação, é um pouco difícil de manter.
  • em alguns casos, um redutor pode depender dos dados de outro (como mover uma mensagem de unread para message , ou até mesmo promover uma mensagem como um novo tópico de message para thread ). Portanto, posso precisar obter dados de outro redutor agora.

Não vejo uma boa solução para esses dois problemas. Na verdade, não estou totalmente certo sobre esses dois problemas, eles são como compensações.

para tal ação, tenho que mantê-lo em vários redutores (ou arquivos depois que meu aplicativo crescer). Em nossa base de código atual, já vimos 5 lojas lidando com a mesma ação, é um pouco difícil de manter.

Se isso é bom ou ruim, depende do seu aplicativo. Na minha experiência, ter muitas lojas (ou redutores Redux) lidando com a mesma ação é realmente muito conveniente porque divide responsabilidades e também permite que as pessoas trabalhem em ramos de recursos sem colidir com o resto da equipe. Na minha experiência, é mais fácil manter mutações não relacionadas separadamente do que uma mutação gigante.

Mas você está certo, há casos em que isso não funciona muito bem. Eu diria que muitas vezes são sintomas de um modelo de estado subótimo. Por exemplo,

em alguns casos, um redutor pode depender de dados de outro (como mover uma mensagem de não lida para mensagem ou até mesmo promover uma mensagem como um novo tópico de mensagem para tópico)

é um sintoma de um problema. Se você tiver que mover muito as coisas dentro do seu estado, talvez a forma do estado precise ser mais normalizada. Em vez de { readMessages, unreadMessages } você pode ter { messagesById, threadsById, messageIdsByThreadIds } . Mensagem lida? Tudo bem, alterne state.messagesById[id].isRead . Quer ler mensagens para um tópico? Pegue state.threadsById[threadId].messageIds , depois messageIds.map(id => state.messagesById[id]) .

Quando os dados são normalizados, você realmente não precisa copiar itens de um array para outro. Na maioria das vezes, você estaria invertendo sinalizadores ou adicionando / removendo / associando / desassociando IDs.

Eu gostaria de tentar isso.

Acabei de escrever uma página de demonstração para tentar redux. Acho que combineReducer trouxe alguma complexidade para mim. E todos os exemplos no repo estão usando combineReducer , ainda há chance de eu poder usar um único objeto de dados imutável como modelo, em vez de um objeto JavaScript que contém meus dados?

@jiyinyiyong Não tenho certeza de qual complexidade você está falando, mas fique à vontade para não usar combineReducers . É apenas um ajudante. Confira um auxiliar semelhante que usa Imutável . Ou escreva o seu, é claro.

Escrevi meu próprio combineReducers() helper principalmente para oferecer suporte a Immutable.js, mas ele pode exigir redutores adicionais que são tratados como redutores de raiz:
https://github.com/jokeyrhyme/wow-healer-bootcamp/blob/master/utils/combineReducers.js

O primeiro argumento corresponde à forma do seu estado e passa os subestados de nível superior para os subredutores correspondentes que você fornece. Esta é mais ou menos igual à versão oficial.

No entanto, é opcional adicionar zero ou mais argumentos adicionais, eles são tratados como outros redutores de raiz. Esses redutores passam do estado completo. Isso permite que as ações sejam despachadas de maneiras que exigem mais acesso do que o padrão de sub-redutor recomendado.

Obviamente, não é uma boa ideia abusar disso, mas eu encontrei o caso estranho em que é bom ter vários sub-redutores, bem como vários redutores de raiz.

Talvez em vez de adicionar vários redutores de raiz, uma etapa intermediária poderia ser usar os argumentos adicionais para definir sub-redutores que precisam de acesso mais amplo (ainda menos do que o acesso total). Por exemplo:

function a (state, action) { /* only sees within a */ return state; }
function b (state, action) { /* only sees within b */ return state; }
function c (state, action) { /* only sees within c */ return state; }

function ab (state, action) { /* partial state containing a and b */ return state; }
function bc (state, action) { /* partial state containing b and c */ return state; }

let rootReducer = combineReducers(
  { a, b, c },
  [ 'a', 'b', ab ],
  [ 'b', 'c', bc ]
);

Vale a pena enviar um PR para redutores parciais e redutores de raiz adicionais? É uma ideia horrível ou algo que poderia ser útil para outras pessoas o suficiente?

Tenho mais vontade de aprender como o halogênio e o olmo estão lidando com esses problemas. JavaScript é uma mistura de vários diagramas de programação e nos leva a muitos caminhos. Agora estou confuso qual é o correto de FRP e fluxo de dados unidirecional.

Descobri que isso também é um ponto de confusão para mim. Apenas postando meu caso de uso / solução aqui.

Tenho três subredutores que lidam com uma variedade de três tipos de recursos diferentes (clientes, projetos, empregos). Quando excluo um cliente ( REMOVE_CLIENT ), todos os projetos e trabalhos relacionados devem ser excluídos. As tarefas são relacionadas aos clientes por meio de um projeto, portanto, o redutor de tarefas deve ter acesso aos projetos para fazer a lógica de junção. O redutor de trabalhos deve obter uma lista de todos os IDs de projeto que pertencem ao cliente que está sendo excluído e, em seguida, remover qualquer trabalho correspondente da loja.

Resolvi esse problema com o seguinte middleware:

export default store => next => action => {
  next({ ...action, getState: store.getState });
}

Para que toda ação tenha acesso ao armazenamento raiz via action.getState() .

@ rhys-vdw obrigado por isso! Um middleware realmente útil :)

@ rhys-vdw Obrigado por isso - parece uma boa solução. Como você lida com casos extremos / integridade referencial, por exemplo, quando uma entidade é compartilhada por duas (ou mais) outras entidades ou apontando para um registro não existente durante a exclusão. Apenas ansioso para ouvir sua opinião sobre isso.
@gaearon Existe uma maneira documentada no Redux de resolver esse tipo de problema de integridade referencial de entidades normalizadas?

Não existe uma forma específica. Eu imagino que se possa automatizar isso definindo um esquema e gerando redutores com base nesse esquema. Eles então “saberiam” como coletar o “lixo” quando algo fosse excluído. No entanto, tudo isso é muito ondulado e requer esforço de implementação :-). Pessoalmente, não acho que a exclusão aconteça com frequência suficiente para justificar uma limpeza real. Eu normalmente viraria um campo status no modelo e me certificaria de não exibir os itens excluídos ao selecioná-los. Ou, é claro, você pode atualizar na exclusão, se não for uma operação comum.

@gaearon Felicidades pela resposta super rápida. Sim ... acho que se tem o mesmo esforço para lidar com integridade referencial ao usar referências com bancos de dados baseados em documentos como o Mongo. Eu acho que vale a pena ter um estado puro sem nenhuma entidade lixo flutuando. Não tenho certeza se essas entradas fantasmas podem atrapalhar você, especialmente quando você tem um monte de operações CRUD em seu estado.

No entanto, também acho que seria ótimo se Redux tivesse suporte para ambos - entidades normalizadas e incorporadas. E pode-se escolher uma estratégia adequada ao seu propósito. Mas eu acho que para entidades incorporadas deve-se usar nested reducers que é um anti-padrão de acordo com a documentação do Redux.

Achei difícil acessar o restante do estado da loja em um redutor. Isso torna os combineReducers aninhados realmente difíceis de usar em alguns casos. Seria bom se houvesse métodos para acessar o resto do estado como .parent() ou .root() ou equivalente.

Acessar o estado de outros redutores em um redutor é geralmente um antipadrão.
Geralmente pode ser resolvido por:

  • Removendo essa lógica do redutor e movendo-a para um seletor;
  • Passar informações adicionais para a ação;
  • Permitir que o código de visualização execute duas ações.

Obrigado! Eu concordo com isso. Acabei de descobrir que ficou mais complexo depois de tentar passar o estado raiz para redutores :-)

Sim, o problema de passar o estado raiz é que os redutores se tornam acoplados à forma de estado um do outro, o que complica qualquer refatoração ou mudança na estrutura de estado.

Ok, vejo claramente os benefícios de ter um estado normalizado e tratar a parte redux do meu tipo de estado como um banco de dados.

Ainda estou pensando se a abordagem de (sinalizando entidades para exclusão) ou @ rhys-vdw é melhor (resolver as referências e remover referências relacionadas no local) .

Alguma ideia / experiência do mundo real?

Se redux fosse mais parecido com olmo e o estado não fosse normalizado, então o desacoplamento dos redutores da forma do estado faz sentido. No entanto, como o estado é normalizado, parece que os redutores geralmente precisam acessar outras partes do estado do aplicativo.

Os redutores já estão transitivamente acoplados à aplicação, pois os redutores dependem de ações. Você não pode reutilizar redutores trivialmente em outro aplicativo redux.

Trabalhar com o problema colocando o acesso de estado em ações e usando middleware parece uma forma muito mais feia de acoplamento do que permitir que os redutores acessem o estado raiz diretamente. Agora, as ações que geralmente não exigem isso também são acopladas à forma do estado e há mais ações do que redutores e mais lugares para mudar.

Passar o estado à vista em ações talvez isole melhor as mudanças de forma de estado, mas se o estado adicional for necessário à medida que recursos são adicionados, muitas ações precisam ser atualizadas.

O IMHO permitindo o acesso dos redutores ao estado raiz resultaria em menos acoplamento geral do que as soluções alternativas atuais.

@kurtharriger : observe que embora o padrão comum recomendado seja estruturar Redux estado por domínio e usar combineReducers para gerenciar cada domínio separadamente, isso é _justa_ uma recomendação. É inteiramente possível escrever seu próprio redutor de nível superior que gerencie as coisas de maneira diferente, incluindo a passagem de todo o estado para funções específicas de sub-redutor. Em última análise, você realmente só tem _uma _ função redutora, e como você decompô-la internamente depende inteiramente de você. combineReducers é apenas um utilitário útil para um caso de uso comum.

A resposta do Redux FAQ sobre a divisão da lógica de negócios também tem uma boa discussão sobre esse tópico.

Sim, outra opção seria apenas usar um único redutor em uma função gigante e / ou escrever sua própria função combineReducer em vez de usar a função integrada.
Ter um método combineReducers integrado que adiciona um argumento de estado raiz adicional pode ser útil, mas os PRs que tentaram adicionar essa funcionalidade foram fechados como um antipadrão. Eu só queria argumentar que as alternativas propostas IMHO resultam em um acoplamento muito mais geral e talvez isso não deva ser considerado um antipadrão. Além disso, se o estado raiz foi adicionado como um argumento adicional, não é como se você fosse forçado a usar, portanto, o acoplamento ainda é uma opção.

Queremos que esse acoplamento seja explícito. É explícito quando você faz isso manualmente e é literalmente N linhas de código onde N é quantos redutores você tem. Apenas não use combineReducers e escreva o redutor principal à mão.

Eu sou novo em redux / react, mas humildemente perguntaria isto: se redutores compostos acessando o estado um do outro é um anti-padrão porque adiciona acoplamento entre áreas díspares da forma de estado, eu argumentaria que este acoplamento já está ocorrendo em mapStateToProps ( ) de connect (), uma vez que essa função fornece o estado raiz e os componentes puxam de qualquer lugar / qualquer lugar para obter seus adereços.

Se as subárvores de estado devem ser encapsuladas, os componentes devem acessar o estado por meio de acessores que ocultam essas subárvores, de modo que a forma de estado interna a esses escopos possa ser gerenciada sem refatoração.

À medida que aprendo redux, estou lutando com o que imagino ser um problema de longo prazo com meu aplicativo, que é o acoplamento estreito da forma de estado em todo o aplicativo - não por redutores, mas por mapStateToProps - como uma barra lateral de comentário que precisa saber o nome do usuário auth (cada um reduzido por um redutor de comentário vs redutor de auth, por exemplo.) Estou experimentando envolver todo o acesso de estado em métodos de acesso que fazem esse tipo de encapsulamento, mesmo em mapStateToProps, mas sinto que posso estar falando algo errado árvore.

@jwhiting : esse é um dos vários propósitos de usar "funções de seletor" para extrair os dados que você precisa do estado, particularmente em mapStateToProps . Você pode ocultar o estado de sua forma para que haja um número mínimo de lugares que realmente precisam saber onde state.some.nested.field está. Se você ainda não viu, vá para a biblioteca de utilitários Reselect , e a seção Computing Derived Data dos documentos Redux.

Sim. Você também não precisa usar Selecionar novamente se o seu volume de dados for pequeno, mas ainda usar seletores (escritos à mão). Confira o exemplo shopping-cart para esta abordagem.

@markerikson @gaearon faz sentido e irei explorar isso, obrigado. O uso de seletores em mapStateToProps para qualquer coisa fora da preocupação principal desse componente (ou talvez para todos os acessos de estado) protegeria os componentes de mudanças na forma de estado estrangeiro. Gostaria de encontrar uma maneira de tornar o encapsulamento de estado mais estritamente aplicado em meu projeto, de modo que a disciplina de código não seja a única proteção para ele e recebo sugestões lá.

Manter todo o código em um único redutor não é realmente uma boa separação de
preocupações. Se o seu redutor for chamado pelo root, você pode chamá-lo manualmente
e passar explicitamente o estado raiz como eu acho que você está sugerindo, mas se o seu
a hierarquia do redutor pai contém combineReducers, você deve copiá-la
Fora. Então, por que não torná-lo mais compossível para que você não precise rasgá-lo
mais tarde ou recorrer a thunks como a maioria das pessoas parece fazer?
No sábado, 16 de abril de 2016 às 20:27 Josh Whiting [email protected]
escrevi:

@markerikson https://github.com/markerikson @gaearon
https://github.com/gaearon que faz sentido e irei explorar isso,
obrigado. Usando seletores em mapStateToProps para qualquer coisa fora disso
a principal preocupação do componente (ou talvez para todo o acesso do estado) protegeria
componentes de mudanças na forma de estado estrangeiro. Eu gostaria de encontrar um caminho
para tornar o encapsulamento de estado mais estritamente aplicado em meu projeto, no entanto,
para que a disciplina do código não seja a única proteção para isso e bem-vinda
sugestões lá.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/reactjs/redux/issues/601#issuecomment -210940305

@kurtharriger : todos nós concordamos que manter cada pedaço da lógica do redutor em uma única função é uma má ideia e que o trabalho deve ser delegado a subfunções de alguma forma. No entanto, todos terão ideias diferentes sobre como fazer isso e necessidades diferentes para sua própria aplicação. O Redux simplesmente fornece combineReducers como um utilitário embutido para um caso de uso comum. Se você não quiser usar esse utilitário como ele existe atualmente, você não precisa - você é totalmente bem-vindo para estruturar seus redutores de sua própria maneira, seja escrevendo tudo à mão ou escrevendo sua própria variação de combineReducers que faz as coisas da maneira mais adequada às suas necessidades.

Já que estamos falando sobre padrões e separação de interesses, na verdade estou encontrando algo relacionado, mas com ações.

Ou seja, o padrão ajax shouldFetch que vejo em vários lugares parece ser um acoplamento forte ao formato do estado. Por exemplo, aqui . Nesse exemplo, e se eu reutilizar o redutor, mas não tiver o objeto de entidades na raiz do estado? Essa ação então falhará. Qual é a recomendação aqui? Devo adicionar ações específicas do caso? Ou uma ação que aceita um seletor?

@shadowii Ter um criador de ação importando um seletor parece bom para mim. Eu colocaria seletores com redutores para que o conhecimento sobre a forma do estado seja localizado nesses arquivos.

Qual é o problema de ter diferentes redutores tratando do mesmo tipo de ação?
Por exemplo, se eu tenho um account e um auth redutores, ambos lidam com o tipo de ação LOGIN_SUCCESS (com o token de autenticação e os dados da conta na carga útil), cada atualizando apenas a fatia de estado que gerenciam. Já usei essa abordagem algumas vezes quando uma ação afeta diferentes partes do estado.

Qual é o problema de ter diferentes redutores tratando do mesmo tipo de ação?

Não há problema, é um padrão muito comum e útil. Na verdade, é a _razão_ que as ações existem: se as ações mapeadas para redutores 1: 1, não haveria muito sentido em ter ações.

@ rhys-vdw tentei usar o middleware que você postou.

customMiddleare.js

const customMiddleware =  store => next => action => {
  next({ ...action, getState: store.getState });
};

e na criação da minha loja eu tenho

import customMiddleware from './customMiddleware';

var store = createStore(
        rootReducer,
        initialState,
        applyMiddleware(
            reduxPromise,
            thunkMiddleware,
            loggerMiddleware,
            customMiddleware
        )
    );
return store;

Eu estou recebendo o seguinte erro:

applyMiddleware.js? ee15: 49 TypeError não detectado: middleware não é uma função (...)
(função anônima) @ applyMiddleware.js? ee15: 49
(função anônima) @ applyMiddleware.js? ee15: 48
createStore @ createStore.js? fe4c: 65
configureStore @ configureStore.js? ffde: 45
(função anônima) @ index.jsx? fdd7: 25
(função anônima) @ bundle.js: 1639
webpack_require @ bundle.js: 556
fn @ bundle.js: 87 (função anônima) @ bundle.js: 588
webpack_require @ bundle.js: 556
(função anônima) @ bundle.js: 579
(função anônima) @ bundle.js: 582

@ mars76 : Provavelmente você não está importando algo corretamente. Se esse for realmente todo o conteúdo de customMiddleware.js , então você não está exportando _qualquer coisa_. Em geral, você está passando quatro middlewares para applyMiddleware e, presumivelmente, um deles não é uma referência válida. Dê um passo para trás, remova todos eles, adicione novamente um de cada vez, veja qual não é válido e descubra por quê. Verifique as declarações de importação e exportação, verifique novamente como você deve inicializar as coisas, etc.

Oi,

Obrigado @markerikson

Converti a sintaxe abaixo e agora está tudo bem:

customMiddleware.js

export function customMiddleware(store) { return function (next) { return function (action) { next(Object.assign({}, action, { getState: store.getState })); }; }; } console.log(customMiddleware);

No entanto, estou tentando acessar a loja no próprio Action Creator (não no redutor). Preciso fazer uma chamada Async e passar por diferentes partes do estado da loja gerenciadas por vários redutores.

Isso é o que estou fazendo. Não tenho certeza se essa é a abordagem certa.

Eu despacho uma ação com os dados e dentro do redutor eu agora tenho acesso a todo o Estado e estou extraindo as partes necessárias que eu preciso e criando um Objeto e despachando outra ação com esses dados que estarão disponíveis no meu Criador de Ações onde eu estou fazendo uma chamada Asyn usando esses dados.

Minha maior preocupação é que os Redutores agora estão chamando métodos criadores de ação.

Agradeço quaisquer comentários.

obrigado

@ mars76 : novamente, você _nunca _ deve tentar despachar de dentro de um Redutor.

Para acessar a loja dentro de seus criadores de ação, você pode usar o middleware redux-thunk . Você também pode querer ler a pergunta FAQ sobre o compartilhamento de dados entre redutores , bem como a essência que escrevi com alguns exemplos de usos comuns de conversão .

Muito obrigado @markerikson.

Meu mal. Eu não sabia sobre o getState () em redux-thunk. Isso torna meu trabalho muito mais fácil e deixa meus redutores puros.

@gaearon quando você mencionou ter um criador de ação importando um seletor , estava em sua mente que todo o estado seria passado para o criador de ação para transferência para o seletor, ou estou faltando alguma coisa?

@tacomanator : Para sua informação, Dan normalmente não responde a pings aleatórios em problemas Redux atualmente. Muitas outras prioridades.

Na maioria das vezes, se você está usando um seletor em um criador de ação, você está fazendo isso dentro de uma lógica. Thunks têm acesso a getState , portanto, toda a árvore de estado está acessível:

import {someSelector} from "./selectors";

function someThunk(someParameter) {
    return (dispatch, getState) => {
        const specificData = someSelector(getState(), someParameter);

        dispatch({type : "SOME_ACTION", payload : specificData});    
    }
}

@markerikson obrigado, isso faz sentido. Para sua informação, estou perguntando principalmente sobre o uso de dados derivados de seletores para lógica de negócios de validação em vez de despachá-los para um redutor, mas isso também me dá o que pensar.

Um truque rápido que usei para combinar redutores em um único foi

const reducers = [ reducerA, reducerB, ..., reducerZ ];

let reducer = ( state = initialState, action ) => {
    return reducers.reduce( ( state, reducerFn ) => (
        reducerFn( state, action )
    ), state );
};

onde reducerA até reducerZ são as funções redutoras individuais.

@brianpkelley : sim, é basicamente https://github.com/acdlite/reduce-reducers .

@markerikson Ah bom, pela primeira vez usando examinava este problema. Pulei para o final por volta de 1/2 no meio lol

Heh, claro.

Para sua informação, você pode querer ler o FAQ: "Eu tenho que usar combineReducers ?" e seções de Redutores de estruturação nos documentos. Além disso, minha série de tutoriais "Redux prático" tem algumas postagens que demonstram algumas técnicas avançadas de redutor (consulte a Parte 7 e a Parte 8).

Meus dois centavos ao usar a lógica redux é aumentar a ação do estado raiz na função transform .

Exemplo:

const formInitLogic = createLogic({
  type: 'FORM_INIT',
  transform({ getState, action }, next) {
    const state = getState();
    next({
      ...action,
      payload: {
        ...action.payload,
        property1: state.branch1.someProperty > 5,
        property2: state.branch1.anotherProperty + state.branch2.yetAnotherProperty,
      },
    });
  },
});

acho que é mais sobre a estrutura do código:
"não acesse ações do subestado diretamente"
depois de usar redux em muitos projetos, descobri que a melhor estrutura de código é lidar com cada subestado como um componente completamente separado, cada um desses componentes tem sua própria pasta e lógica. Para implementar essa ideia, eu uso a seguinte estrutura de arquivos:

  • raiz redux

    • A exportação de

    • exportação de middleware.js, applyMiddleware para todos os middleware

    • configureStore.js createStore usando (reducers.js, middleware.js)

    • actions.js exportam ações que despacham ações das pastas de estados <== ISTO

    • estado1

    • initial.js este arquivo contém o estado inicial (eu uso imutável para criar um registro)

    • action-types.js este arquivo contém os tipos de ação (eu uso dacho como espelho principal)

    • actions.js este arquivo contém as ações de estado

    • reducer.js este arquivo contém o redutor de estado

    • state2

    • initial.js

    • action-types.js

      ....

Por quê ?
1- existem 2 tipos de ações em cada aplicativo:

  • ações de estado (redux / state1 / actions.js) responsabilidade dessas ações para apenas alterar o estado, você não deve disparar essas ações diretamente, sempre use ações do aplicativo

    • ações do aplicativo (redux / actions.js) responsabilidade dessas ações para fazer alguma lógica do aplicativo e pode disparar ações de estado

2- adicionalmente, usando esta estrutura, você pode criar um novo estado apenas copiando, colando uma pasta e renomeando-a;)

E quando 2 redutores precisarem alterar a mesma propriedade?

Digamos que eu tenha um redutor que gostaria de dividir em arquivos diferentes e que existe uma propriedade chamada 'erro', que eu altero quando as solicitações http falham por um motivo ou outro.

Agora, em algum ponto, o redutor se tornou muito grande, então decidi dividi-lo em vários redutores, mas todos eles precisam alterar essa propriedade de 'erro'.

Então o que?

@ Wassap124 Não é possível que dois redutores gerenciem o mesmo estado. Você quer dizer que duas ações precisam mudar a mesma propriedade?

Sem mais detalhes, você pode preferir usar uma conversão para despachar uma ação "definir erro".

@ rhys-vdw Estou um pouco preso tentando dividir um redutor bastante longo.
E quanto à sua sugestão, isso não contradiz a regra de nenhum efeito colateral?

@ Wassap124 , @ rhys-vdw: para esclarecer, não é possível que dois redutores gerenciem o mesmo estado _se você estiver usando apenas combineReducers _. No entanto, você certamente pode escrever outra lógica de redutor para se adequar ao seu próprio caso de uso - consulte https://redux.js.org/recipes/structuringreducers/beyondcombinereducers .

Se você fizer uma espeleologia rápida ...

O combineReducers retorna uma assinatura de função assim

return function combination(state = {}, action) {

consulte https://github.com/reduxjs/redux/blob/master/src/combineReducers.js#L145

depois de criar um fechamento para fazer referência ao objeto redutor real.

Portanto, você pode adicionar algo simples como isso para controlar globalmente as ações no caso de precisar hidratar e recarregar o estado do armazenamento.

const globalReducerHandler = () => {
  return (state = {}, action) => {
    if (action.type === 'DO_GLOBAL_THING') {
      return globallyModifyState(state)
    }
    return reducers(state, action)
  }
}

Isso pode ou não ser um antipadrão, mas o pragmatismo sempre foi mais importante.

Em vez de chamar um redutor de outro redutor (que é um antipadrão, porque globalReducerHandler não tem nada a ver com o redutor que chama), use reduceReducers , que é essencialmente compose para redutores (bem, é literalmente composição em uma (Action ->) mônada).

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

vraa picture vraa  ·  3Comentários

caojinli picture caojinli  ·  3Comentários

jbri7357 picture jbri7357  ·  3Comentários

elado picture elado  ·  3Comentários

timdorr picture timdorr  ·  3Comentários