Redux: Abordagem alternativa para ações assíncronas

Criado em 27 dez. 2015  ·  44Comentários  ·  Fonte: reduxjs/redux

Estive explorando uma alternativa para a forma como as ações assíncronas são feitas no redux e agradeceria quaisquer comentários que outras pessoas possam ter sobre o que eu fiz.

Para ilustrar minha abordagem, mudei o exemplo assíncrono em meu clone do redux: https://github.com/winstonewert/redux/tree/master/examples/async

Normalmente, as ações externas são realizadas tornando os criadores da ação assíncronos. No caso do exemplo assíncrono, o criador da ação fetchPosts despacha uma ação REQUEST_POSTS para indicar o início da solicitação, seguida por RECEIVE_POSTS assim que as postagens voltam da api.

No meu exemplo, todos os criadores de ação são síncronos. Em vez disso, há uma função que retorna a lista de ações assíncronas que deveriam estar ocorrendo atualmente com base no estado. Veja meu exemplo aqui: https://github.com/rackt/redux/compare/master...winstonewert : master # diff-8a94dc7aa7bdc6e5390c9216a69761f8R12

A função doReactions assina o armazenamento e garante que o estado real das solicitações feitas atualmente corresponda ao estado retornado pelo estado doReactions ao iniciar ou cancelar as solicitações.

Qual é a diferença?

1) A função de reação é uma função pura do estado. Isso facilita o teste.
2) A lógica real de quais solicitações fazer é mais simples. Veja a função de poucas linhas em meu exemplo, em comparação com as várias peças de lógica espalhadas por meio de contêineres e criadores de ação antes.
3) Facilita o cancelamento de solicitações.

Alguma ideia?

discussion feedback wanted

Comentários muito úteis

Eu também tenho pensado muito sobre formas alternativas de lidar com os efeitos colaterais no redux e espero não sequestrar seu tópico enquanto despejo mentalmente alguns dos problemas que vejo com algumas abordagens atuais e por que e como eu acho que isso é grande passo na direção certa, apesar de sua aparente simplicidade.

O problema dos efeitos colaterais nos criadores de ação

Em linguagens puramente funcionais, os efeitos colaterais são sempre elevados à borda do aplicativo e retornados ao tempo de execução para execução. No Elm, os redutores retornam uma tupla contendo o novo estado e quaisquer efeitos que devem ser executados. Métodos com esta assinatura, entretanto, ainda não são combináveis ​​com outros redutores redux.

O local óbvio (mas possivelmente não o melhor) para executar os efeitos colaterais no redux tornou-se os criadores de ação e várias opções de middleware diferentes foram desenvolvidas para oferecer suporte a esse padrão. No entanto, acho que as abordagens atuais de middleware são mais uma solução alternativa para não serem capazes de retornar os efeitos colaterais como um conceito de primeira classe dos redutores.

Embora as pessoas ainda estejam construindo coisas incríveis com o redux e seja um grande passo em frente e muito mais simples e pragmático do que a maioria das alternativas, vejo alguns problemas em ter efeitos colaterais dentro dos criadores de ação:

  • O estado implícito está oculto
  • Duplicação da lógica de negócios
  • Suposições de contexto e / ou dependências reduzem a capacidade de reutilização
  • Criadores de ação com efeitos colaterais são difíceis de testar
  • Não pode ser otimizado ou em lote

O estado implícito está oculto

No aplicativo de contador, incrementAsync cria um tempo limite e somente após sua conclusão o estado do aplicativo é atualizado. Se você quiser, por exemplo, exibir um indicador visual de que uma operação de incremento está em andamento, a visualização não pode inferir isso a partir do estado do aplicativo. Este estado está implícito e oculto.

Embora às vezes elegante, não tenho tanta certeza sobre a proposta de usar geradores como orquestradores de criadores de ação, uma vez que o estado implícito está oculto e não pode ser serializado facilmente.

Usando redux-thunk ou similar, você pode enviar várias mensagens para o redutor, informando-o quando a operação de incremento foi iniciada e quando foi concluída, mas isso cria um conjunto diferente de problemas.

Retroceder o estado até um ponto em que a operação de incremento é marcada como em andamento após a conclusão do efeito não irá regenerar o efeito colateral e, portanto, permanecerá em andamento indefinidamente.

Sua proposta parece resolver este problema. Uma vez que os efeitos colaterais são criados a partir do estado, a intenção deve ser expressa com o estado resultante de uma forma ou de outra, portanto, se alguém reverter o armazenamento para um estado anterior onde a ação foi iniciada, então as reações iniciarão o efeito novamente, em vez de deixar o estado no limbo.

Duplicação da lógica de negócios

É natural que as ações gerem um efeito colateral apenas quando o aplicativo está em um estado específico. No redux, se um criador de ação requer estado, deve ser uma função simples e pura ou explicitamente fornecida com o estado.

Como um exemplo simples, vamos começar com o aplicativo de contador de exemplo e queremos mudar o contador para uma cor de fonte aleatória sempre que o contador for um múltiplo de 5.

Como a geração de números aleatórios é impura, o local sugerido para colocar esse comportamento é no criador da ação. No entanto, existem várias ações diferentes que podem alterar o valor do contador, incremento, decremento, incrementAsync, incrementIfOdd (que não precisa ser modificado neste caso).

incremento e decremento anteriormente não exigiam nenhum estado, pois eram manipulados anteriormente no redutor e, portanto, tinham acesso ao valor atual, mas uma vez que um redutor não pode ter ou retornar efeitos colaterais (geração de número aleatório), essas funções agora se tornam criadores de ações impuras que precisam para saber o valor atual do contador para determinar se é necessário selecionar uma nova cor de fonte aleatória e esta lógica precisa ser duplicada em todos os criadores de ação de contador.

Uma alternativa possível para fornecer explicitamente o estado atual seria usar redux-thunk e retornar um retorno de chamada para acessar o estado atual. Isso permite que você evite a modificação de todos os locais onde as ações são criadas para fornecer o valor atual, mas agora requer que o criador da ação saiba onde no estado do aplicativo global o valor está armazenado e isso limita a capacidade de reutilizar o mesmo contador várias vezes dentro a mesma aplicação ou em aplicações diferentes onde o estado pode ser estruturado de forma diferente.

Suposições de contexto e / ou dependências reduzem a capacidade de reutilização

Ao revisitar novamente o exemplo do contador, você notará que há apenas uma instância do contador. Embora seja trivial ter muitos contadores na página que exibem / atualizam o mesmo estado, modificações adicionais no contador são necessárias se você quiser que cada contador use um estado diferente.

Isso foi discutido antes. Como criar uma lista genérica como redutor e intensificador de componentes?

Se o contador usasse apenas tipos de ação simples, seria relativamente trivial aplicar a arquitetura do

Neste caso, o pai simplesmente envolve os criadores da ação ou despachante para aumentar a mensagem com qualquer contexto necessário, ele pode então chamar o redutor diretamente com o estado localizado.

Embora o exemplo do

incrementIfOdd depende do middleware para determinar o estado atual e, portanto, precisa saber sua localização dentro do estado do aplicativo.

incrementAsync eventualmente despacha diretamente uma ação de incremento que não é exposta ao componente pai e, portanto, não pode ser agrupada com contexto adicional.

Embora sua proposta não trate diretamente desse problema, se incrementAsync foi implementado como uma ação simples que alterou o estado para {counter: 0, incrementAfterDelay: 1000} para acionar o efeito colateral em um ouvinte da loja, então incrementAsync se torna uma mensagem simples. incrementIfOdd é puro, portanto, pode ser implementado no redutor ou pode receber o estado explicitamente ... Assim, torna-se possível aplicar a arquitetura elm novamente, se desejado.

Criadores de ação com efeitos colaterais são difíceis de testar

Acho que é bastante óbvio que os efeitos colaterais serão mais difíceis de testar. Uma vez que seus efeitos colaterais se tornem condicionais ao estado atual e à lógica de negócios, eles se tornam não apenas mais difíceis, mas também mais importantes para serem testados.

Sua proposta permite criar facilmente um teste em que uma transição de estado criará um estado contendo as reações desejadas sem realmente executar nenhuma delas. As reações também são mais fáceis de testar, pois não precisam de nenhum estado condicional ou lógica de negócios.

Não pode ser otimizado ou em lote

Uma postagem recente no blog de John A De Goes discutiu o problema com tipos de dados opacos, como IO ou Task para expressar efeitos. Ao usar descrições declarativas de efeitos colaterais em vez de tipos opacos, você tem o potencial de otimizar ou combinar efeitos posteriormente.

Uma arquitetura moderna para FP

Thunks, promessas e geradores são opacos e, portanto, otimizações como lote e / ou supressão de chamadas de API duplicadas devem ser tratadas explicitamente com funções semelhantes a fetchPostsIfNeeded .

Sua proposta elimina fetchPostsIfNeeded e parece totalmente viável implementar uma função reactions que poderia otimizar várias solicitações e / ou usar diferentes conjuntos de apis conforme necessário quando mais ou menos dados foram solicitados.

Minha implementação

Recentemente, criei um fork do redux que permite criar redutores que retornam apenas o novo estado como fazem agora ou um objeto especial withEffects contendo o novo estado e uma descrição de quaisquer efeitos a serem executados após o redutor.

Eu não tinha certeza de como fazer isso sem bifurcar redux, pois era necessário modificar compose e combineReducers para eliminar os efeitos sobre os redutores existentes a fim de manter a compatibilidade com o código do redutor existente.

Sua proposta, entretanto, é muito boa, pois não requer a modificação de redux. Além disso, acho que sua solução faz um trabalho melhor ao resolver o problema de estado oculto implícito e é provavelmente mais fácil de combinar ou otimizar as reações resultantes.

Resumo

Assim como o React é "apenas a interface do usuário" e não é muito prescritivo como alguém realmente armazena ou atualiza o estado do aplicativo, Redux é principalmente "apenas a loja" e não é muito prescritivo sobre como lidar com os efeitos colaterais.

Eu nunca culpo ninguém por ser pragmático e fazer as coisas e os muitos contribuidores para redux e o middleware permitiram que as pessoas criassem coisas realmente legais mais rápido e melhor do que era possível anteriormente. É somente com suas contribuições que chegamos até aqui. Agradecimentos especiais a todos que contribuíram.

Redux é incrível. Esses não são problemas necessários com o Redux em si, mas, com sorte, críticas construtivas dos padrões arquitetônicos atuais e das motivações e vantagens potenciais para executar os efeitos após, e não antes, das modificações de estado.

Todos 44 comentários

Abordagem interessante! Parece que o ideal é que ele seja dissociado da natureza da assincronia, como o método usado para executar o XHR, ou mesmo que uma solicitação da web seja a fonte da assincronia em primeiro lugar.

Eu também tenho pensado muito sobre formas alternativas de lidar com os efeitos colaterais no redux e espero não sequestrar seu tópico enquanto despejo mentalmente alguns dos problemas que vejo com algumas abordagens atuais e por que e como eu acho que isso é grande passo na direção certa, apesar de sua aparente simplicidade.

O problema dos efeitos colaterais nos criadores de ação

Em linguagens puramente funcionais, os efeitos colaterais são sempre elevados à borda do aplicativo e retornados ao tempo de execução para execução. No Elm, os redutores retornam uma tupla contendo o novo estado e quaisquer efeitos que devem ser executados. Métodos com esta assinatura, entretanto, ainda não são combináveis ​​com outros redutores redux.

O local óbvio (mas possivelmente não o melhor) para executar os efeitos colaterais no redux tornou-se os criadores de ação e várias opções de middleware diferentes foram desenvolvidas para oferecer suporte a esse padrão. No entanto, acho que as abordagens atuais de middleware são mais uma solução alternativa para não serem capazes de retornar os efeitos colaterais como um conceito de primeira classe dos redutores.

Embora as pessoas ainda estejam construindo coisas incríveis com o redux e seja um grande passo em frente e muito mais simples e pragmático do que a maioria das alternativas, vejo alguns problemas em ter efeitos colaterais dentro dos criadores de ação:

  • O estado implícito está oculto
  • Duplicação da lógica de negócios
  • Suposições de contexto e / ou dependências reduzem a capacidade de reutilização
  • Criadores de ação com efeitos colaterais são difíceis de testar
  • Não pode ser otimizado ou em lote

O estado implícito está oculto

No aplicativo de contador, incrementAsync cria um tempo limite e somente após sua conclusão o estado do aplicativo é atualizado. Se você quiser, por exemplo, exibir um indicador visual de que uma operação de incremento está em andamento, a visualização não pode inferir isso a partir do estado do aplicativo. Este estado está implícito e oculto.

Embora às vezes elegante, não tenho tanta certeza sobre a proposta de usar geradores como orquestradores de criadores de ação, uma vez que o estado implícito está oculto e não pode ser serializado facilmente.

Usando redux-thunk ou similar, você pode enviar várias mensagens para o redutor, informando-o quando a operação de incremento foi iniciada e quando foi concluída, mas isso cria um conjunto diferente de problemas.

Retroceder o estado até um ponto em que a operação de incremento é marcada como em andamento após a conclusão do efeito não irá regenerar o efeito colateral e, portanto, permanecerá em andamento indefinidamente.

Sua proposta parece resolver este problema. Uma vez que os efeitos colaterais são criados a partir do estado, a intenção deve ser expressa com o estado resultante de uma forma ou de outra, portanto, se alguém reverter o armazenamento para um estado anterior onde a ação foi iniciada, então as reações iniciarão o efeito novamente, em vez de deixar o estado no limbo.

Duplicação da lógica de negócios

É natural que as ações gerem um efeito colateral apenas quando o aplicativo está em um estado específico. No redux, se um criador de ação requer estado, deve ser uma função simples e pura ou explicitamente fornecida com o estado.

Como um exemplo simples, vamos começar com o aplicativo de contador de exemplo e queremos mudar o contador para uma cor de fonte aleatória sempre que o contador for um múltiplo de 5.

Como a geração de números aleatórios é impura, o local sugerido para colocar esse comportamento é no criador da ação. No entanto, existem várias ações diferentes que podem alterar o valor do contador, incremento, decremento, incrementAsync, incrementIfOdd (que não precisa ser modificado neste caso).

incremento e decremento anteriormente não exigiam nenhum estado, pois eram manipulados anteriormente no redutor e, portanto, tinham acesso ao valor atual, mas uma vez que um redutor não pode ter ou retornar efeitos colaterais (geração de número aleatório), essas funções agora se tornam criadores de ações impuras que precisam para saber o valor atual do contador para determinar se é necessário selecionar uma nova cor de fonte aleatória e esta lógica precisa ser duplicada em todos os criadores de ação de contador.

Uma alternativa possível para fornecer explicitamente o estado atual seria usar redux-thunk e retornar um retorno de chamada para acessar o estado atual. Isso permite que você evite a modificação de todos os locais onde as ações são criadas para fornecer o valor atual, mas agora requer que o criador da ação saiba onde no estado do aplicativo global o valor está armazenado e isso limita a capacidade de reutilizar o mesmo contador várias vezes dentro a mesma aplicação ou em aplicações diferentes onde o estado pode ser estruturado de forma diferente.

Suposições de contexto e / ou dependências reduzem a capacidade de reutilização

Ao revisitar novamente o exemplo do contador, você notará que há apenas uma instância do contador. Embora seja trivial ter muitos contadores na página que exibem / atualizam o mesmo estado, modificações adicionais no contador são necessárias se você quiser que cada contador use um estado diferente.

Isso foi discutido antes. Como criar uma lista genérica como redutor e intensificador de componentes?

Se o contador usasse apenas tipos de ação simples, seria relativamente trivial aplicar a arquitetura do

Neste caso, o pai simplesmente envolve os criadores da ação ou despachante para aumentar a mensagem com qualquer contexto necessário, ele pode então chamar o redutor diretamente com o estado localizado.

Embora o exemplo do

incrementIfOdd depende do middleware para determinar o estado atual e, portanto, precisa saber sua localização dentro do estado do aplicativo.

incrementAsync eventualmente despacha diretamente uma ação de incremento que não é exposta ao componente pai e, portanto, não pode ser agrupada com contexto adicional.

Embora sua proposta não trate diretamente desse problema, se incrementAsync foi implementado como uma ação simples que alterou o estado para {counter: 0, incrementAfterDelay: 1000} para acionar o efeito colateral em um ouvinte da loja, então incrementAsync se torna uma mensagem simples. incrementIfOdd é puro, portanto, pode ser implementado no redutor ou pode receber o estado explicitamente ... Assim, torna-se possível aplicar a arquitetura elm novamente, se desejado.

Criadores de ação com efeitos colaterais são difíceis de testar

Acho que é bastante óbvio que os efeitos colaterais serão mais difíceis de testar. Uma vez que seus efeitos colaterais se tornem condicionais ao estado atual e à lógica de negócios, eles se tornam não apenas mais difíceis, mas também mais importantes para serem testados.

Sua proposta permite criar facilmente um teste em que uma transição de estado criará um estado contendo as reações desejadas sem realmente executar nenhuma delas. As reações também são mais fáceis de testar, pois não precisam de nenhum estado condicional ou lógica de negócios.

Não pode ser otimizado ou em lote

Uma postagem recente no blog de John A De Goes discutiu o problema com tipos de dados opacos, como IO ou Task para expressar efeitos. Ao usar descrições declarativas de efeitos colaterais em vez de tipos opacos, você tem o potencial de otimizar ou combinar efeitos posteriormente.

Uma arquitetura moderna para FP

Thunks, promessas e geradores são opacos e, portanto, otimizações como lote e / ou supressão de chamadas de API duplicadas devem ser tratadas explicitamente com funções semelhantes a fetchPostsIfNeeded .

Sua proposta elimina fetchPostsIfNeeded e parece totalmente viável implementar uma função reactions que poderia otimizar várias solicitações e / ou usar diferentes conjuntos de apis conforme necessário quando mais ou menos dados foram solicitados.

Minha implementação

Recentemente, criei um fork do redux que permite criar redutores que retornam apenas o novo estado como fazem agora ou um objeto especial withEffects contendo o novo estado e uma descrição de quaisquer efeitos a serem executados após o redutor.

Eu não tinha certeza de como fazer isso sem bifurcar redux, pois era necessário modificar compose e combineReducers para eliminar os efeitos sobre os redutores existentes a fim de manter a compatibilidade com o código do redutor existente.

Sua proposta, entretanto, é muito boa, pois não requer a modificação de redux. Além disso, acho que sua solução faz um trabalho melhor ao resolver o problema de estado oculto implícito e é provavelmente mais fácil de combinar ou otimizar as reações resultantes.

Resumo

Assim como o React é "apenas a interface do usuário" e não é muito prescritivo como alguém realmente armazena ou atualiza o estado do aplicativo, Redux é principalmente "apenas a loja" e não é muito prescritivo sobre como lidar com os efeitos colaterais.

Eu nunca culpo ninguém por ser pragmático e fazer as coisas e os muitos contribuidores para redux e o middleware permitiram que as pessoas criassem coisas realmente legais mais rápido e melhor do que era possível anteriormente. É somente com suas contribuições que chegamos até aqui. Agradecimentos especiais a todos que contribuíram.

Redux é incrível. Esses não são problemas necessários com o Redux em si, mas, com sorte, críticas construtivas dos padrões arquitetônicos atuais e das motivações e vantagens potenciais para executar os efeitos após, e não antes, das modificações de estado.

Estou tentando entender a diferença entre essa abordagem e a saga redux. Estou interessado na alegação de que ele oculta o estado dos geradores implicitamente, porque a princípio, parece que está fazendo a mesma coisa. Mas suponho que isso pode depender de como io.take é implementado. Se a saga só vai processar uma ação se ela estiver atualmente bloqueada naquele yield , então eu definitivamente entendi o que você quis dizer. Mas se redux-saga enfileira ações de forma que io.take retorne ações anteriores, parece que está fazendo a mesma coisa. De qualquer maneira, você tem alguma lógica que pode dispatch ações de forma assíncrona, acionada ouvindo o fluxo de ação.

É um conceito interessante. Conceituando Redux como um fluxo de ação, a partir do qual as transições de estado e os efeitos são acionados. Isso me parece uma visão alternativa do que apenas considerá-lo um processador de estado.

No modelo de sourcing de evento, acho que se resume a se as ações do Redux são "comandos" (solicitações contingentes para realizar uma ação) ou "eventos" (transições atômicas de estado, refletidas em uma visualização plana). Acho que temos uma ferramenta que é flexível o suficiente para ser considerada de qualquer maneira.

Eu também estou um pouco insatisfeito com o status quo dos "criadores de ação inteligente", mas tenho abordado isso de uma forma diferente, em que Redux é mais a loja de eventos - onde as ações são um dos muitos efeitos possíveis que pode ser acionado por alguma camada "controladora" externa. Fatorei o código que seguia essa abordagem em react-redux-controller , embora eu tenha uma ideia incompleta em mente sobre uma maneira potencialmente mais leve de fazer isso. No entanto, seria necessário react-redux para ter um gancho que não tem atualmente, e alguns truques de empacotamento de loja que ainda não resolvi.

Hijinks da loja descritos https://github.com/rackt/redux/issues/1200

Estou tentando entender a diferença entre essa abordagem e a saga redux

Eu não vi a saga redux até depois de apresentar minha abordagem, mas definitivamente existem algumas semelhanças. Mas ainda tenho algumas diferenças:

  1. Minha abordagem não tem acesso ao fluxo de ação, apenas ao estado. redux-saga pode iniciar o processo simplesmente porque houve uma ação. Minha abordagem requer que um redutor faça uma mudança no estado que aciona a função de reação para solicitar a ação.
  2. Minha abordagem requer que todos os estados existam no estado de redux. Redux-saga tem o estado adicional que reside no gerador de saga (em que linha está, os valores das variáveis ​​locais).
  3. Minha abordagem isola a parte assíncrona. A lógica real da reação pode ser testada sem lidar com a funcionalidade assíncrona. A saga junta tudo isso.
  4. A saga reúne peças diferentes da mesma lógica. Minha abordagem força você a dividir uma saga em partes que pertencem ao redutor, às reações e à implementação do tipo de reação.

Basicamente, minha abordagem enfatiza funções puras e manter tudo no estado redux. A abordagem da saga redux enfatiza ser mais expressivo. Acho que há prós e contras, mas gosto mais do meu. Mas sou tendencioso.

Isso parece muito promissor. Acho que seria mais convincente ver um exemplo que separa o mecanismo de reação da lógica do domínio.

Sua proposta elimina fetchPostsIfNeeded e parece totalmente viável implementar uma função de reações que poderia otimizar várias solicitações e / ou usar diferentes conjuntos de apis conforme necessário quando mais ou menos dados foram solicitados.

Do jeito que está, você realmente não poderia fazer isso na função de reações. A lógica de lá precisaria saber quais ações já foram iniciadas (não podemos colocar mais nada nelas), mas a função de reações não tem a informação. O mecanismo de reações que consome a função responses () certamente poderia fazer essas coisas.

Acho que seria mais convincente ver um exemplo que separa o mecanismo de reação da lógica do domínio.

Presumo que você se refira à maneira como a função doReactions () lida com o início / parada do XMLHttpRequest. Tenho explorado diferentes maneiras de fazer isso. O problema é que é difícil encontrar uma maneira genérica de detectar se duas reações são realmente a mesma reação. O isEqual de Lodash quase funciona, mas falha para fechamentos.

Presumo que você se refira à maneira como a função doReactions () lida com o início / parada do XMLHttpRequest.

Não, quero dizer apenas que, em seu exemplo, toda a configuração para definir o conceito de uma reação está misturada com a lógica de domínio de quais dados estão sendo buscados, bem como os detalhes de como esses dados estão sendo buscados. Parece-me que os aspectos genéricos devem ser fatorados em algo que seja menos acoplado aos detalhes específicos do exemplo.

Não, quero dizer apenas que, em seu exemplo, toda a configuração para definir o conceito de uma reação está misturada com a lógica de domínio de quais dados estão sendo buscados, bem como os detalhes de como esses dados estão sendo buscados. Parece-me que os aspectos genéricos devem ser fatorados em algo que seja menos acoplado aos detalhes específicos do exemplo.

Hmm ... Acho que não podemos dizer a mesma coisa por lógica de domínio.

A meu ver, a função responses () encapsula a lógica do domínio e é separada da função doReactions (), que lida com a lógica de como as reações são aplicadas. Mas você parece querer dizer algo diferente ...

Do jeito que está, você realmente não poderia fazer isso na função de reações. A lógica de lá precisaria saber quais ações já foram iniciadas (não podemos colocar mais nada nelas), mas a função de reações não tem a informação. O mecanismo de reações que consome a função responses () certamente poderia fazer essas coisas.

Eu quis dizer principalmente que, se um único evento disparou uma mudança de estado em que vários componentes solicitaram as mesmas informações, ele pode ser capaz de otimizá-los. Você está certo, entretanto, que não é por si só suficiente determinar se um efeito colateral de uma mudança de estado anterior ainda está pendente e, portanto, a solicitação adicional é desnecessária.

Eu estava inicialmente pensando que talvez fosse possível manter todos os estados dentro do estado do aplicativo, mas quando comecei a pensar sobre o problema recente do isOn deva ser armazenado no estado do aplicativo, o interval real isOn deve estar no estado de aplicativo, mas não está sozinho, não é um estado suficiente neste caso.

Eu quis dizer principalmente que, se um único evento disparou uma mudança de estado em que vários componentes solicitaram as mesmas informações, ele pode ser capaz de otimizá-los. Você está certo, entretanto, que não é por si só suficiente determinar se um efeito colateral de uma mudança de estado anterior ainda está pendente e, portanto, a solicitação adicional é desnecessária.

Eu estava pensando em mesclar ou enviar solicitações em lote. Eliminar duplicatas deve funcionar bem. Na verdade, ele deve lidar com o caso de alterações de estado pendentes muito bem também, uma vez que elas ainda serão retornadas da função de reações (e, portanto, desdupulicadas) até que a resposta do servidor volte.

Eu estava inicialmente pensando que talvez fosse possível manter todos os estados dentro do estado do aplicativo, mas quando comecei a pensar sobre o problema recente do cronômetro, percebi que embora o fato de o cronômetro estar ligado deva ser armazenado no estado do aplicativo, o objeto de intervalo real associado a este cronômetro precisa ser armazenado em outro lugar. isOn deve estar no estado do aplicativo, mas não está sozinho, não é um estado suficiente neste caso.

Do jeito que eu penso sobre isso, as reações pendentes atuais são como seus componentes de reação. Tecnicamente, eles têm algum estado interno, mas nós os modelamos como uma função do estado atual.

Hmm ... Acho que não podemos dizer a mesma coisa por lógica de domínio.

A meu ver, a função responses () encapsula a lógica do domínio e é separada da função doReactions (), que lida com a lógica de como as reações são aplicadas. Mas você parece querer dizer algo diferente ...

Eu meio que peguei todo o módulo /reactions/index como um todo, mas sim, eu concordaria que a função reactions é puramente lógica de domínio. Mas em vez de estar em um módulo específico de domínio, ele é empacotado com o clichê de doReactions . Isso não é para criticar sua metodologia, apenas torna mais difícil entender rapidamente a separação entre o código da biblioteca e o código do aplicativo.

Então, o próprio doReactions me parece estar fortemente acoplado a um método particular do ato particular de buscar dados de uma API. Suponho que uma biblioteca de reações mais detalhada possa ser uma forma de registrar manipuladores para diferentes tipos de efeitos.

Isso não é derrubar seu método; Acho essa abordagem muito atraente.

Não tenho certeza se o estado do componente de reação é uma boa analogia, pois a maioria dos estados de reação
deve estar no estado do aplicativo, mas aparentemente deve haver alguma forma
para manter o estado entre os eventos de despacho que não podem ser colocados no
loja.

Eu acho que esse tipo de estado é o que @yelouafi se refere como estado de controle e
talvez sagas seja uma boa maneira de modelar o estado não serializável do
sistema como um observador / ator independente.

Acho que ficaria menos preocupado com o estado oculto da saga se as sagas
respondeu apenas a eventos gerados pelo aplicativo (reações) em vez de pelo usuário
eventos iniciados (ações), pois isso permitiria ao redutor de aplicativos usar o
estado atual e qualquer lógica condicional de negócios para determinar se o
a aplicação deve permitir o efeito colateral desejado sem duplicar
logíca de negócios.
Na segunda-feira, 4 de janeiro de 2016 às 17:56 Winston Ewert [email protected]
escreveu:

Eu quis dizer principalmente que se um único evento desencadeou uma mudança de estado em que
vários componentes solicitaram as mesmas informações, então ele pode ser capaz de
otimizá-los. Você está certo, no entanto, que não é por si só suficiente para
determinar se um efeito colateral de uma mudança de estado anterior ainda está pendente
e, portanto, a solicitação adicional é desnecessária.

Eu estava pensando em mesclar ou enviar solicitações em lote. Eliminando duplicatas
deve funcionar muito bem. Na verdade, ele deve lidar com o caso de estado pendente
mudanças também, uma vez que ainda serão retornadas do
função de reações (e, portanto, desdupulicada) até a resposta do servidor
volta.

Eu estava inicialmente pensando que talvez fosse possível manter todo o estado dentro do aplicativo
estado, mas quando comecei a pensar sobre o problema recente do cronômetro,
percebi que embora o fato de o cronômetro estar ligado deva ser armazenado em
o estado do aplicativo, o objeto de intervalo real associado a este
o cronômetro precisa ser armazenado em outro lugar. isOn deve estar no aplicativo
estado, mas não está sozinho, não é um estado suficiente neste caso.

A maneira como eu penso sobre isso, as reações pendentes atuais são como o seu
componentes de reação. Tecnicamente, eles têm algum estado interno, mas nós modelamos
em função do estado atual.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/rackt/redux/issues/1182#issuecomment -168858051.

Isso não é para criticar sua metodologia, apenas torna mais difícil entender rapidamente a separação entre o código da biblioteca e o código do aplicativo.

Isso é totalmente justo.

Então, o próprio doReactions me parece estar fortemente acoplado a um método específico do ato específico que busca dados de uma API. Suponho que uma biblioteca de reações mais detalhada possa ser uma forma de registrar manipuladores para diferentes tipos de efeitos.

sim. Ainda estou tentando descobrir a melhor maneira de dividir isso. É complicado pelo problema de verificação de igualdade.

Não tenho certeza se o estado do componente de reação é uma boa analogia, pois a maioria dos estados de reação
deve estar no estado do aplicativo, mas aparentemente deve haver alguma forma
para manter o estado entre os eventos de despacho que não podem ser colocados no
loja.

Desculpe, acho que estraguei a analogia. Meu ponto não é comparar o estado de ação externa com o estado do componente de reação, mas sim o estado do DOM. O intervalo ou XMLHttpRequest são semelhantes aos elementos DOM que reagem, cria e destrói. Você simplesmente diz para reagir como o DOM atual deve ser e faz com que aconteça Da mesma forma, você simplesmente retorna o conjunto de reações externas atuais e a estrutura cancela ou inicia a ação para torná-la verdadeira.

Também acho essa abordagem muito interessante. Você já pensou em usar vários doReactions , que levam diferentes mapeamentos de estado? Acho que seria semelhante a cyclejs, onde você pode construir drivers reutilizáveis:

function main(action$) {
  const state$ = action$.startWith(INITIAL_STATE).scan(reducer);

  return { 
    DOM: state$.map(describeDOM),
    HTTP: state$.map(describeRequests),
    ...
  };
}

Uma diferença é que você não consulta os drivers de eventos para obter o fluxo de ação ( const someEvent$ = sources.DOM.select('.class').events('click') ), mas especifica as ações diretamente no coletor ( <button onClick={() => dispatch(action())} /> ) como você fez para solicitações HTTP também.

Acho que a analogia do React funciona muito bem. Eu não consideraria o DOM como o estado interno, mas sim a API com a qual ele funciona, enquanto o estado interno é formado pelas instâncias do componente e o dom virtual.

Aqui está uma ideia para a API (usando React; HTTP também pode ser construído assim):

// usage
const describe = (state, dispatch) => <MyComponent state={state} dispatch={dispatch} />;
const driver = createReactDOMDriver({ container } /* opts */);
store.subscribe(() => driver.update(describe(store.getState(), store.dispatch)); 
// (could be simplified further to eg. `store.use(driver, describe)` )

// implementation
const createReactDOMDriver = ({ container }) => {
  return {
    update: (element) => ReactDOM.render(element, container),
    destroy: () => ReactDOM.unmountComponentAtNode(container),
  };
};

Eu faria com que describe levasse getState (em vez de um instantâneo do estado) e dispatch . Dessa forma, pode ser tão assíncrono quanto quiser.

Você já pensou em usar várias doReactions, que usam mapeamentos de estados diferentes?

Eu pensei brevemente sobre isso, e estou indo e voltando um pouco agora. É natural ter diferentes bibliotecas de reações que fazem coisas diferentes, uma para o DOM, uma para o http, uma para os timers, uma para o áudio da web, etc. Cada uma pode fazer as otimizações / comportamentos adequados ao seu caso. Mas parece menos útil se você tiver um aplicativo que executa várias ações externas pontuais.

Eu teria que descrever, pegar getState (em vez de um instantâneo de estado) e despachar. Dessa forma, pode ser tão assíncrono quanto quiser.

Eu não iria. Em minha opinião, queremos restringir o assíncrono sempre que possível, e não fornecer maneiras adicionais de usá-lo. Qualquer coisa que você queira chamar getState () deve ser feita no redutor ou na função de reações. (Mas essa é minha mentalidade purista, e talvez haja um caso pragmático para não segui-la.)

Ponto justo. Ainda não pensei muito sobre o mapeamento entre sua ideia e o exemplo de @taurose . Apressadamente, assumi que describe era a função reactions , mas isso pode não ser verdade.

Mas sim, concordo que limitar async é ideal, porque se eu entendo o impulso da sua ideia, queremos que as continuações sejam puras e que mapeiem 1: 1 com aspectos específicos no estado, como a presença de um membro da matriz descrevendo a intenção de que um determinado efeito está em andamento. Dessa forma, realmente não importa se eles são executados várias vezes, e não há nenhum aspecto oculto de um processo sendo paralisado em algum lugar no meio do fluxo do qual outros processos possam depender implicitamente.

Eu teria que descrever, pegar getState (em vez de um instantâneo de estado) e despachar. Dessa forma, pode ser tão assíncrono quanto quiser.

describe é chamado a cada mudança de estado, então não vejo necessidade para isso. Isso não significa que não possa ser assíncrono. Considere os componentes de reação: você não chamaria getState dentro de seus métodos de renderização ou manipuladores de eventos para obter o estado atual, mas sim lê-lo a partir de props.

Mas você está certo quanto ao fato de que ele não pode (não deve) fazer nada de forma assíncrona por si só; deve deixar isso para o driver e apenas passar algum estado mapeado e / ou callbacks.

assumido descrever era a função de reações, mas isso pode não ser verdade.

Pelo que eu posso dizer, é praticamente a mesma coisa. Uma diferença seria que reactions não recebe dispatch . Portanto, enquanto describe retorna callbacks que criam e despacham ações, reactions retorna criadores de ações.

@winstonewert é uma longa discussão e não tenho tempo para ler agora ou verificar seu código, mas talvez @yelouafi possa responder a você.

O projeto redux-saga originou-se de longas discussões aqui

Também estou usando o conceito da saga há mais de um ano em um aplicativo de produção, e a implementação é menos expressiva, mas não baseada em geradores. Aqui estão alguns pseudo-exemplos que dei do conceito de redux:

A implementação aqui está longe de ser perfeita, mas apenas dá uma ideia.

@yelouafi está ciente dos problemas inerentes ao uso de geradores que ocultam o estado fora do redux, e que é complicado começar uma saga em um back-end e transmitir esse estado oculto para o front-end para aplicativos universais (se realmente necessário?)

A saga redux é redux-thunk como Free é para a mônada de IO. Os efeitos são declarativos e não executados agora, podem ser examinados e executados em um interpretador (que você pode personalizar no futuro)

Eu entendo seu ponto sobre o estado oculto dentro dos geradores. Mas será que a loja Redux é a verdadeira fonte da verdade de um aplicativo Redux? Acho que não. Redux grava ações e as reproduz. Você sempre pode repetir essas ações para recriar a loja. O redux store é como uma visualização de consulta CQRS do log de eventos. Isso não significa que deve ser a única projeção desse registro de eventos. Você pode projetar o mesmo log de eventos em diferentes visualizações de consulta e ouvi-los em sagas que podem gerenciar seu estado com geradores, objetos mutáveis ​​globais ou redutores, qualquer que seja a tecnologia.

Eu, que criar o conceito da saga com redutor não é uma má ideia conceitualmente, e eu concordo com você, é uma troca de decisão.
Pessoalmente, depois de mais de 1 ano de uso de sagas em produção, não me lembro de nenhum caso de uso em que teria sido útil poder fazer um instantâneo do estado de uma saga e restaurá-lo mais tarde, então prefiro a expressividade dos geradores mesmo se eu perder isso característica.

Espero que nada do que estou dizendo tenha parecido um ataque à saga redux. Eu estava falando sobre como isso difere da abordagem que eu propus.

Eu entendo seu ponto sobre o estado oculto dentro dos geradores. Mas será que a loja Redux é a verdadeira fonte da verdade de um aplicativo Redux? Acho que não. Redux grava ações e as reproduz. Você sempre pode repetir essas ações para recriar a loja. O redux store é como uma visualização de consulta CQRS do log de eventos. Isso não significa que deve ser a única projeção desse registro de eventos. Você pode projetar o mesmo log de eventos em diferentes visualizações de consulta e ouvi-los em sagas que podem gerenciar seu estado com geradores, objetos mutáveis ​​globais ou redutores, qualquer que seja a tecnologia.

Eu realmente não entendo seu ponto aqui. Você parece estar argumentando que uma saga é uma projeção do registro de eventos? Mas isso não. Se eu repetir as ações, não vou chegar ao mesmo lugar nas sagas se a saga depender de eventos assíncronos. Parece-me inescapável que as sagas produzem um estado que não está no repositório de estado do redux, nem é uma projeção do registro de eventos.

Pelo que eu posso dizer, é praticamente a mesma coisa. Uma diferença seria que as reações não são despachadas. Portanto, enquanto o describe retorna os callbacks que criam e despacham as ações, as reações retorna os criadores das ações.

Acordado. Em princípio, o react poderia usar a mesma interface, todos os tratadores de evento tomariam um criador de ação que seria despachado quando o evento disparasse.

Quanto mais penso nisso, acho que pode haver muita sinergia entre essa abordagem e as sagas. Eu concordo totalmente com os quatro pontos delineados por @winstonewert. Acho que é uma coisa boa que as reações não possam ver as ações iniciadas pelo usuário, pois isso evita o estado oculto e garante que a lógica de negócios nos redutores não precise ser duplicada nos criadores de ações ou sagas. No entanto, percebi que os efeitos colaterais geralmente criam um estado não serializável que não pode ser armazenado no repositório react, intervalos, objetos dom, requisições http, etc. sagas, rxjs, baconjs, etc. são perfeitos para este estado de controle externo não serializável.

doReactions poderia ser substituído por uma saga e a fonte de eventos para sagas deveria ser reações, não ações.

Espero que nada do que estou dizendo tenha parecido um ataque à saga redux

De jeito nenhum. Tenho acompanhado a discussão, mas não quero comentar sem olhar mais de perto para o seu código.

À primeira vista. Parece que você só reage às mudanças de estado. Como eu disse, foi uma olhada rápida. Mas parece que tornará a implementação de fluxos complexos ainda mais difícil do que a abordagem do olmo (onde você assume o estado e a ação). isso significa que você terá que armazenar ainda mais estado de controle na loja (onde as alterações de estado do aplicativo por si só são insuficientes para inferir as reações relevantes)

Claro, nada pode vencer funções puras. Acho que os redutores são ótimos para expressar transições de estado, mas ficam muito estranhos quando você os transforma em máquinas de estado.

isso significa que você terá que armazenar ainda mais estado de controle na loja (onde as alterações de estado do aplicativo por si só são insuficientes para inferir as reações relevantes)

Sim. Esse me parece ser o principal aspecto diferenciador dessa abordagem. Mas eu me pergunto se esse problema poderia ser tornado transparente, na prática, se diferentes tipos de efeito pudessem ser agrupados em diferentes "drivers"? Imagino que seja muito fácil para as pessoas simplesmente escolher os drivers que desejam ou escrever seus próprios para novos efeitos.

No entanto, percebi que os efeitos colaterais geralmente criam um estado não serializável que não pode ser armazenado no repositório react, intervalos, objetos dom, requisições http, etc. sagas, rxjs, baconjs, etc. são perfeitos para este estado de controle externo não serializável.

Não estou vendo o que você ainda é.

Acho que os redutores são ótimos para expressar transições de estado, mas ficam muito estranhos quando você os transforma em máquinas de estado.

Eu concordo. Se você estiver escrevendo à mão uma máquina de estados complexa, temos um problema. (Na verdade, seria legal se pudéssemos converter um gerador em um redutor).

Mas eu me pergunto se esse problema poderia ser tornado transparente, na prática, se diferentes tipos de efeito pudessem ser agrupados em diferentes "drivers"? Imagino que seja muito fácil para as pessoas simplesmente escolher os drivers que desejam ou escrever seus próprios para novos efeitos.

Não tenho certeza do que você está pensando aqui. Posso ver drivers diferentes fazendo coisas úteis diferentes, mas eliminando o estado de controle?

@winstonewert não, não estou considerando nada como um ataque. Eu nem tive tempo de realmente olhar seu código :)

Eu realmente não entendo seu ponto aqui. Você parece estar argumentando que uma saga é uma projeção do registro de eventos? Mas isso não. Se eu repetir as ações, não vou chegar ao mesmo lugar nas sagas se a saga depender de eventos assíncronos. Parece-me inescapável que as sagas produzem um estado que não está no repositório de estado do redux, nem é uma projeção do registro de eventos.

Não, não estou, a loja redux é uma projeção, mas a saga é um ouvinte simples e comum.

A saga (também chamada de gerenciador de processos) não é um conceito novo, ela se origina no mundo CQRS e foi amplamente utilizada em sistemas back-end no passado.

A saga não é a projeção de um log de eventos para uma estrutura de dados, é uma peça de orquestração que pode ouvir o que está acontecendo em seu sistema e emitir reações, o resto são detalhes de implementação. Geralmente sagas estão ouvindo um log de eventos (e talvez outras coisas externas, como o tempo ...) e podem produzir novos comandos / eventos. Além disso, quando você reproduz eventos em sistemas de back-end, geralmente desativa os efeitos colaterais acionados por sagas.

A diferença é que, em sistemas de back-end, a saga costuma ser na verdade uma projeção do log de eventos: para mudar seu estado, ele mesmo precisa emitir eventos e ouvi-los. No redux-saga da forma como está atualmente implementado, seria mais difícil reproduzir o log de eventos para restaurar o estado do saga.

Não tenho certeza do que você está pensando aqui. Posso ver drivers diferentes fazendo coisas úteis diferentes, mas eliminando o estado de controle?

Nah, não eliminando, apenas tornando-o uma preocupação de implementação subjacente, para a maioria dos propósitos.

Parece-me que há um consenso muito forte na comunidade Redux de que armazenar o estado do domínio na loja é uma grande vitória (caso contrário, por que você usaria o Redux?). Um pouco menos é o consenso de que armazenar o estado da IU é uma vitória, em vez de tê-lo encapsulado em componentes. Depois, há a ideia de sincronizar o estado do navegador na loja, como a URL (redux-simple-router) ou os dados do formulário. Mas essa parece ser a fronteira final, de armazenar o status / estágio do processo de longa duração na loja.

Desculpe se isso é uma tangente, mas acho que uma abordagem altamente geral com boa usabilidade do desenvolvedor teria que ter os seguintes recursos:

  • Faça com que o usuário típico não precise se preocupar com a forma como os efeitos são representados na loja. Eles devem interagir com APIs simples, que abstraem esses detalhes.
  • Faça com que os efeitos sejam facilmente combináveis. Deve parecer natural fazer coisas como controlar o fluxo e efeitos que dependem de outros efeitos. É aqui, claro, que uma abstração de gerador realmente brilha. Ele funciona bem com a maioria dos fluxos de controle, sendo os fechamentos uma exceção notável. Mas é fácil ver como fluxos assíncronos complicados podem ser expressos em redux-saga ou react-redux-controller.
  • Faça com que o status do efeito possa ser facilmente mostrado aos consumidores de outras lojas, quando desejado. Isso permitiria que você fizesse coisas como apresentar o status de um processo de múltiplos efeitos ao usuário.
  • Talvez isso seja óbvio, mas qualquer subsistema que encapsula estado sincroniza esse estado com Redux, despachando ações.

Para esse segundo ponto, acho que deveria haver algo muito semelhante à saga redux. Pode ficar muito próximo do que tenho em mente com seus invólucros call . Mas uma saga teria que ser "acelerável", de certa forma, para permitir que você a desserializasse em um estado intermediário.

Tudo isso é uma tarefa difícil, mas, praticamente falando, acho que se houver grandes ganhos a serem obtidos por ter um registro de ação central e serializável, rastreando o estado de um aplicativo inteiro em um nível muito granular, este seria o maneira de alavancá-lo. E eu acho que pode haver grandes vitórias por aí. Estou imaginando uma maneira muito mais simples de instrumentar aplicativos com análises de usuário e desempenho. Estou imaginando uma testabilidade realmente incrível, onde diferentes subsistemas são acoplados apenas por meio do estado.

Posso ter perdido o curso agora, então vou deixar por isso mesmo :)

@acjay acho que concordamos com você nesses pontos, o problema é encontrar essa implementação que resolva tudo isso corretamente :)

Mas parece difícil ter uma API expressiva com geradores e a possibilidade de viagem no tempo e instantâneo / restaurar o estado ... Talvez fosse possível memorizar a execução do efeito para que possamos facilmente restaurar o estado dos geradores ...

Não tenho certeza, mas isso pode impedir while(true) { ... } sagas de estilo. O looping seria apenas uma consequência da progressão de estado?

@acjay @slorber

Como expliquei em (https://github.com/yelouafi/redux-saga/issues/22#issuecomment-168872101) Viajar no tempo sozinho (ou seja, sem recarga quente) é possível para sagas. Tudo que você precisa para levar uma saga a um ponto específico é a sequência de efeitos produzida desde o início até aquele ponto, bem como seu resultado (resolver ou rejeitar). Então você apenas acionará o gerador com essa sequência

No branch master real (ainda não lançado no npm). Os Sagas apoiam o monitoramento, eles despacham todos os efeitos produzidos, bem como seus resultados como ações para a loja; eles também fornecem informações de hierarquia para rastrear o gráfico de fluxo de controle.

Esse log de efeito pode ser explorado para repetir uma Saga até um determinado ponto: não há necessidade de fazer chamadas de API reais, pois o log já contém as respostas anteriores.

Nos exemplos de repo, há um exemplo de monitor saga (implementado como um middleware Redux). Ele escuta o log de efeito e mantém uma estrutura de árvore interna (bem construída preguiçosamente). Você pode imprimir um traço do fluxo enviando uma ação {type: 'LOG_EFFECT'} para a loja

Aqui está uma captura de um log de efeito do exemplo assíncrono

saga-log-async

Editar: link de imagem fixa desculpe

Intrigante! E essa imagem de ferramentas de desenvolvimento é _awesome_.

Que legal :)

Na verdade, aquele monitor de saga é muito legal.

Pensando bem, me parece que a saga está resolvendo dois problemas. Em primeiro lugar, ele lida com os efeitos assíncronos. Em segundo lugar, ele lida com interações de estado complexas que, de outra forma, teriam exigido uma máquina de estado escrita à mão desagradável em um redutor.

Minha abordagem aborda apenas o primeiro problema. Não encontrei necessidade para o segundo problema. Provavelmente ainda não escrevi código redux suficiente para encontrá-lo.

Sim, mas me pergunto se há uma maneira de fundir as duas ideias. O wrapper call redux-saga é um nível bastante simples de indireção sobre um efeito, mas supondo que você pudesse inicializar o middleware com drivers para diferentes tipos de efeitos, você poderia representá-los como dados JSONable, separados da função que está realmente sendo chamado. O driver cuidaria dos detalhes de despacho das mudanças de estado subjacentes para a loja.

Isso pode representar muita complexidade adicional para poucos benefícios práticos. Mas tentar seguir essa linha de pensamento até o fim.

Ok, eu montei mais de uma biblioteca e portei o exemplo do mundo real para usá-lo:

Em primeiro lugar, temos a implementação de reações:
https://github.com/winstonewert/redux-reactions/blob/master/src/index.js
A interface tem três funções: startReactions leva o armazenamento, uma função de reação e um mapeamento de nomes para os drivers. fromEmitter e fromPromiseFactory criam drivers.

Aqui, o exemplo chama startReactions para habilitar o sistema:
https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/store/configureStore.dev.js#L28

A configuração básica das reações está aqui:
https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/reactions/index.js.
A função de reações, na verdade, apenas itera através dos componentes que reagem ao roteador instanciando procurando por aqueles com uma função react () para descobrir as reações reais necessárias para aquela página.

A implementação do tipo de reação github api está aqui: https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/reactions/api.js. Isso é principalmente copiar / colar do middleware usado no exemplo original. O ponto crítico está aqui: https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/reactions/api.js#L79 , onde usa fromPromiseFactory para criar o driver a partir de uma função que retorna promessas.

Veja uma função de reações específicas do componente aqui: https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/containers/RepoPage.js#L80.

Os criadores da reação e a lógica comum estão em https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/reactions/data.js

Oi pessoal! Raise acaba de publicar um aprimorador de loja que permite que você use um sistema de efeitos semelhante à arquitetura Elm! Espero que possamos aprender e melhorar todas essas abordagens daqui para frente para atender a todas as necessidades da comunidade: sorriso:

https://github.com/raisemarketplace/redux-loop

Quem estiver interessado na discussão pode querer ver mais detalhes sobre minha ideia aqui: https://github.com/winstonewert/redux-reactions/issues/7

Você também pode olhar para um branch aqui, onde retrabalho o aplicativo do contador para ficar mais elaborado usando meu padrão:
https://github.com/winstonewert/redux-reactions/tree/elmish/examples/counter

Também descobri que estou reinventando a abordagem usada aqui: https://github.com/ccorcos/elmish

Ei @yelouafi , você poderia repassar o link para a ideia do monitor de saga? Isso é algo realmente ótimo! O link parece estar morto (404). Eu adoraria ver mais!

(Eu acredito que isso está relacionado. Desculpe se esse é um lugar errado)

Podemos tratar todos os efeitos da mesma forma que a renderização de DOM?

  1. jQuery é um driver DOM com interface imperativa. React é um driver DOM com interface declarativa. Então, ao invés de ordenar: "desabilite esse botão", declaramos: "precisamos desabilitar esse botão" e o driver decide quais manipulações DOM devem ser feitas. Em vez de ordenar: " GET \product\123 ", declaramos: "precisamos desses dados" e o driver decide quais solicitações enviar / cancelar.
  2. Usamos componentes React como API para driver DOM. Vamos usá-los para fazer interface com outros drivers também.

    • <button ...> - nós construímos nossa camada de Visualização a partir de componentes React "normais"

    • <Map ...> - usamos componentes "wrapper" para transformar a interface imperativa de alguma biblioteca em declarativa. Nós os usamos da mesma forma que os componentes "normais", mas internamente eles são na verdade drivers.

    • <Chart ...> - pode ser qualquer uma das opções acima, dependendo da implementação. Portanto, a linha entre os componentes "normais" e os drivers já está borrada.

    • <Http url={'/product/'+props.selectedProductId} onSuccess={props.PRODUCT_LOADED} /> (ou "smart" <Service...> ) - construímos nossa camada de serviço com base nos componentes do driver (sem IU)

As camadas View e Service são descritas por meio dos componentes React. E nossos componentes de nível superior (conectados) os unem.
Desta forma, nossos redutores permanecem puros e não introduzimos nenhum novo meio para lidar com os efeitos.

Não tenho certeza de como new Date ou Math.random encaixam aqui.

É sempre possível converter uma API imperativa em declarativa?
Você acha que esta é uma visão viável?

Obrigado

Considerando que temos sagas e outras ferramentas incríveis para ações assíncronas, acho que podemos encerrar isso com segurança agora. Confira # 1528 para algumas novas direções interessantes (além de apenas async também).

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

Questões relacionadas

parallelthought picture parallelthought  ·  3Comentários

vraa picture vraa  ·  3Comentários

mickeyreiss-visor picture mickeyreiss-visor  ·  3Comentários

timdorr picture timdorr  ·  3Comentários

ramakay picture ramakay  ·  3Comentários