As lojas ou ações de fluxo (ou ambas) devem tocar nos serviços externos?

122

Se as lojas mantiverem seu próprio estado e tiverem a capacidade de chamar serviços de rede e armazenamento de dados ao fazê-lo ... nesse caso, as ações serão apenas passadores de mensagens idiotas,

-OU-

... as lojas devem ser idiotas de dados imutáveis ​​das ações (e as ações são aquelas que buscam / enviam dados entre fontes externas? A loja, neste caso, atuaria como modelos de exibição e seria capaz de agregar / filtrar seus dados antes de definir sua própria base de estado nos dados imutáveis ​​que foram alimentados pela ação.

Parece-me que deveria ser um ou outro (em vez de uma mistura de ambos). Se sim, por que um é preferido / recomendado em relação ao outro?

plaxdan
fonte
2
Este post poderia ajudar code-experience.com/...
Markus-ipse
Para aqueles que avaliam as várias implementações do padrão de fluxo, recomendo dar uma olhada no Redux github.com/rackt/redux Stores são implementadas como funções puras que recebem o estado atual e emitem uma nova versão desse estado. Como são funções puras, a questão de poder ou não chamar os serviços de rede e armazenamento é tirada de suas mãos: eles não podem.
plaxdan

Respostas:

151

Eu vi o padrão de fluxo implementado nos dois sentidos e, depois de ter feito as duas coisas (inicialmente seguindo a abordagem anterior), acredito que os armazenamentos devem ser destinatários burros dos dados das ações e que o processamento assíncrono de gravações deve viver no criadores de ação. ( Async leituras podem ser tratadas de maneira diferente .) Na minha experiência, isso tem alguns benefícios, em ordem de importância:

  1. Suas lojas se tornam completamente síncronas. Isso torna a lógica da sua loja muito mais fácil de seguir e muito fácil de testar - basta instanciar uma loja com um determinado estado, enviar uma ação e verificar se o estado mudou conforme o esperado. Além disso, um dos conceitos principais do fluxo é impedir despachos em cascata e impedir vários despachos de uma só vez; isso é muito difícil de fazer quando suas lojas fazem processamento assíncrono.

  2. Todos os despachos de ação acontecem dos criadores da ação. Se você manipula operações assíncronas em suas lojas e deseja manter os manipuladores de ação de suas lojas síncronos (e você deve obter as garantias de envio único de fluxo), suas lojas precisarão acionar ações adicionais de SUCESSO e FAIL em resposta a assíncronas em processamento. Colocar esses despachos nos criadores de ações ajuda a separar os trabalhos dos criadores de ações e das lojas; além disso, você não precisa procurar na lógica da sua loja para descobrir de onde as ações estão sendo despachadas. Uma ação assíncrona típica nesse caso pode ser algo assim (altere a sintaxe das dispatchchamadas com base no sabor do fluxo que você está usando):

    someActionCreator: function(userId) {
      // Dispatch an action now so that stores that want
      // to optimistically update their state can do so.
      dispatch("SOME_ACTION", {userId: userId});
    
      // This example uses promises, but you can use Node-style
      // callbacks or whatever you want for error handling.
      SomeDataAccessLayer.doSomething(userId)
      .then(function(newData) {
        // Stores that optimistically updated may not do anything
        // with a "SUCCESS" action, but you might e.g. stop showing
        // a loading indicator, etc.
        dispatch("SOME_ACTION_SUCCESS", {userId: userId, newData: newData});
      }, function(error) {
        // Stores can roll back by watching for the error case.
        dispatch("SOME_ACTION_FAIL", {userId: userId, error: error});
      });
    }

    A lógica que pode ser duplicada em várias ações deve ser extraída em um módulo separado; neste exemplo, seria esse módulo SomeDataAccessLayer, que lida com a solicitação real do Ajax.

  3. Você precisa de menos criadores de ação. Isso é menos importante, mas é bom ter. Conforme mencionado no item 2, se suas lojas tiverem manipulação de envio de ação síncrona (e deveriam), você precisará disparar ações extras para lidar com os resultados de operações assíncronas. Fazer os despachos nos criadores de ação significa que um único criador de ação pode despachar todos os três tipos de ação manipulando o resultado do próprio acesso assíncrono aos dados.

Michelle Tilley
fonte
15
Acho que o que origina a chamada da API da Web (criador da ação x loja) é menos importante do que o fato de que o retorno de chamada de sucesso / erro deve criar uma ação. Portanto, o fluxo de dados é sempre: ação -> expedidor -> lojas -> visualizações.
Fisherwebdev
1
Colocar a lógica de solicitação real dentro de um módulo de API seria melhor / mais fácil de testar? Portanto, seu módulo de API pode apenas retornar uma promessa da qual você envia. O criador da ação apenas envia com base na resolução / falha após enviar uma ação inicial 'pendente'. A questão que resta é como o componente escuta esses 'eventos', pois não tenho certeza de que o estado da solicitação seja mapeado para o estado da loja.
Backdesk
@backdesk É exatamente o que faço no exemplo acima: despachar uma ação pendente inicial ( "SOME_ACTION"), usar uma API para fazer uma solicitação ( SomeDataAccessLayer.doSomething(userId)) que retorne uma promessa e, nas duas .thenfunções, despachar ações adicionais. O estado da solicitação pode (mais ou menos) mapear para armazenar o estado, se o aplicativo precisar saber sobre o status do estado. Como este mapas é até o aplicativo (por exemplo, talvez cada comentário tem um estado erro individual, a la Facebook, ou talvez haja um componente de erro global)
Michelle Tilley
@MichelleTilley "um dos conceitos principais do fluxo é evitar despachos em cascata e vários despachos ao mesmo tempo; isso é muito difícil de fazer quando suas lojas fazem processamento assíncrono". Esse é um ponto chave para mim. Bem dito.
51

Eu twittou esta pergunta para os desenvolvedores no Facebook e a resposta que recebi de Bill Fisher foi:

Ao responder à interação de um usuário com a interface do usuário, eu faria a chamada assíncrona nos métodos do criador de ações.

Mas quando você tem um código ou outro motorista não humano, uma ligação da loja funciona melhor.

O importante é criar uma ação no retorno de chamada de erro / sucesso para que os dados sempre se originem com ações

Markus-ipse
fonte
Enquanto isso faz sentido, alguma idéia do porquê a call from store works better when action triggers from non-human driver ?
SharpCoder 29/01
@SharpCoder Eu acho que se você tem um live-ticker ou algo semelhante, você realmente não precisa acionar uma ação e quando você faz isso na loja, provavelmente precisa escrever menos código, já que a loja pode acessar instantaneamente o estado e emitir uma mudança.
Florian Wendelborn 19/02
8

As lojas devem fazer tudo, incluindo buscar dados e sinalizar para os componentes que os dados da loja foram atualizados. Por quê? Como as ações podem ser leves, descartáveis ​​e substituíveis sem influenciar comportamentos importantes. Todos os comportamentos e funcionalidades importantes acontecem na loja. Isso também evita a duplicação de comportamentos que, de outra forma, seriam copiados em duas ações muito semelhantes, mas diferentes. As lojas são sua única fonte de (lidar com a) verdade.

Em todas as implementações do Flux que vi, Actions são basicamente cadeias de eventos transformadas em objetos, como tradicionalmente você teria um evento chamado "anchor: clicked", mas no Flux seria definido como AnchorActions.Clicked. Eles são tão "burros" que a maioria das implementações tem objetos Dispatcher separados para realmente despachar os eventos para as lojas que estão ouvindo.

Pessoalmente, gosto da implementação do Flux pelo Reflux, onde não há objetos Dispatcher separados e os objetos Action fazem o envio por eles mesmos.


edit: O Flux do Facebook, na verdade, busca "criadores de ação", para que eles usem ações inteligentes. Eles também preparam a carga útil usando as lojas:

https://github.com/facebook/flux/blob/19a24975462234ddc583ad740354e115c20b881d/examples/flux-chat/js/actions/ChatMessageActionCreators.js#L27 (linhas 27 e 28)

O retorno de chamada na conclusão acionaria uma nova ação dessa vez com os dados buscados como carga útil:

https://github.com/facebook/flux/blob/19a24975462234ddc583ad740354e115c20b881d/examples/flux-chat/js/utils/ChatWebAPIUtils.js#L51

Então acho que essa é a melhor solução.

Rygu
fonte
O que é essa implementação do Reflux? Eu não ouvi falar disso. Sua resposta é interessante. Você quer dizer que a implementação da sua loja deve ter a lógica para fazer chamadas de API e assim por diante? Eu pensei que as lojas deveriam apenas receber dados e apenas atualizar seus valores. Eles filtram ações específicas e atualizam alguns atributos de suas lojas.
Jeremy D
O Reflux é uma ligeira variação do Flux do Facebook: as lojas github.com/spoike/refluxjs gerenciam todo o domínio "Modelo" do seu aplicativo, em comparação a Ações / Despachantes que apenas juntam e colam as coisas.
Rygu 3/09/14
1
Então, eu estive pensando sobre isso um pouco mais e (quase) respondi à minha própria pergunta. Eu teria adicionado como resposta aqui (para que outros votem), mas aparentemente eu sou muito pobre em karma no stackoverflow para poder postar uma resposta ainda. Então, aqui está um link: groups.google.com/d/msg/reactjs/PpsvVPvhBbc/BZoG-bFeOwoJ
plaxdan
Obrigado pelo link do grupo do Google, parece realmente informativo. Também sou mais fã de tudo que passa pelo despachante, e uma lógica muito simples na loja, basicamente, atualizar os dados deles é tudo. @ Rygu vou verificar o refluxo.
Jeremy D
Eu editei minha resposta com uma visão alternativa. Parece que as duas soluções são possíveis. Eu certamente escolheria a solução do Facebook em detrimento de outras.
Rygu 3/09/14
3

Fornecerei um argumento a favor de ações "burras".

Ao colocar a responsabilidade de coletar dados de exibição em suas Ações, você as associa aos requisitos de dados de suas visualizações.

Por outro lado, as Ações genéricas, que descrevem declarativamente a intenção do usuário, ou alguma transição de estado em seu aplicativo, permitem que qualquer Loja que responda a essa Ação transforme a intenção em um estado personalizado especificamente para as visualizações nele subscritas.

Isso se presta a lojas mais numerosas, porém menores e mais especializadas. Eu argumento por esse estilo porque

  • isso oferece mais flexibilidade na maneira como as visualizações consomem Dados da loja
  • As lojas "inteligentes", especializadas nas visualizações que as consomem, serão menores e menos acopladas a aplicativos complexos do que as ações "inteligentes", das quais potencialmente muitas visualizações dependem

O objetivo de uma loja é fornecer dados para visualizações. O nome "Ação" sugere para mim que seu objetivo é descrever uma alteração no meu Aplicativo.

Suponha que você precise adicionar um widget a uma exibição existente do Painel, que mostra alguns novos dados agregados sofisticados que sua equipe de back-end acabou de lançar.

Com as ações "inteligentes", pode ser necessário alterar a ação do "painel de atualização" para consumir a nova API. No entanto, "Atualizando o painel" em um sentido abstrato não mudou. Os requisitos de dados de suas visualizações são o que mudou.

Com Ações "burras", você pode adicionar uma nova Loja para o novo widget consumir e configurá-la para que, quando receber o tipo de ação "painel de atualização", envie uma solicitação para os novos dados e a exponha a o novo widget quando estiver pronto. Faz sentido para mim que, quando a camada de visualização precisar de mais ou de dados diferentes, as coisas que eu altero serão as fontes desses dados: Lojas.

Carlos Lalimarmo
fonte
2

O flux- react -router-demo da gaeron possui uma boa variação de utilidade da abordagem 'correta'.

Um ActionCreator gera uma promessa de um serviço de API externo e passa a promessa e três constantes de ação para uma dispatchAsyncfunção em um Proxy / Dispatcher estendido. dispatchAsyncsempre enviará a primeira ação, por exemplo, 'GET_EXTERNAL_DATA' e, uma vez que a promessa retorne, enviará 'GET_EXTERNAL_DATA_SUCCESS' ou 'GET_EXTERNAL_DATA_ERROR'.

William Myers
fonte
1

Se você quer um dia ter um ambiente de desenvolvimento comparável ao que você vê no famoso vídeo Inventando sobre princípios , de Bret Victor , você deve usar lojas idiotas que são apenas uma projeção de ações / eventos dentro de uma estrutura de dados, sem nenhum efeito colateral. Também ajudaria se suas lojas fossem realmente membros da mesma estrutura global de dados imutáveis, como no Redux .

Mais explicações aqui: https://stackoverflow.com/a/31388262/82609

Sebastien Lorber
fonte