Onde a solicitação do ajax deve ser feita no aplicativo Flux?

194

Estou criando um aplicativo react.js com arquitetura de fluxo e estou tentando descobrir onde e quando uma solicitação de dados do servidor deve ser feita. Existe algum exemplo para isso. (Não aplicativo TODO!)

Eniz Gülek
fonte

Respostas:

127

Sou um grande defensor de colocar operações de gravação assíncrona nos criadores de ação e operações de leitura assíncrona na loja. O objetivo é manter o código de modificação do estado da loja em manipuladores de ação totalmente síncronos; isso os torna simples de raciocinar e simples de testar na unidade. Para impedir várias solicitações simultâneas para o mesmo ponto de extremidade (por exemplo, leitura dupla), moverei o processamento real da solicitação para um módulo separado que usa promessas para impedir as diversas solicitações; por exemplo:

class MyResourceDAO {
  get(id) {
    if (!this.promises[id]) {
      this.promises[id] = new Promise((resolve, reject) => {
        // ajax handling here...
      });
    } 
    return this.promises[id];
  }
}

Enquanto as leituras na loja envolvem funções assíncronas, há uma ressalva importante de que as lojas não se atualizam nos manipuladores assíncronos, mas, em vez disso, acionam uma ação e somente acionam quando a resposta chega. Os manipuladores dessa ação acabam realizando a modificação do estado.

Por exemplo, um componente pode fazer:

getInitialState() {
  return { data: myStore.getSomeData(this.props.id) };
}

A loja teria um método implementado, talvez, algo como isto:

class Store {
  getSomeData(id) {
    if (!this.cache[id]) {
      MyResurceDAO.get(id).then(this.updateFromServer);
      this.cache[id] = LOADING_TOKEN;
      // LOADING_TOKEN is a unique value of some kind
      // that the component can use to know that the
      // value is not yet available.
    }

    return this.cache[id];
  }

  updateFromServer(response) {
    fluxDispatcher.dispatch({
      type: "DATA_FROM_SERVER",
      payload: {id: response.id, data: response}
    });
  }

  // this handles the "DATA_FROM_SERVER" action
  handleDataFromServer(action) {
    this.cache[action.payload.id] = action.payload.data;
    this.emit("change"); // or whatever you do to re-render your app
  }
}
Michelle Tilley
fonte
Você já tentou colocar promessas em cargas úteis de ação? Acho que é mais fácil lidar do que despachar várias ações #
Sebastien Lorber
@SebastienLorber O grande atrativo do fluxo para mim é manter todas as atualizações de estado em um caminho de código síncrono e explicitamente apenas como resultado de despachos de ação, para evitar assincronia dentro das lojas.
precisa
1
@Federico Ainda não está claro para mim qual é a "melhor" solução. Eu tenho experimentado essa estratégia de carregamento de dados combinada com a contagem do número de solicitações assíncronas pendentes. Infelizmente, ele fluxé injetado nas lojas após a construção, portanto, não há uma ótima maneira de obter ações no método de inicialização. Você pode encontrar boas idéias nas bibliotecas de fluxo isomoróficas do Yahoo; isso é algo que o Fluxxor v2 deve suportar melhor. Sinta-se à vontade para me enviar um e-mail se quiser conversar mais sobre isso.
Michelle Tilley
1
data: resultdeveria ser data : data, certo? não existe result. talvez seja melhor renomear os parâmetros de dados para carga útil ou algo assim.
precisa
2
Eu achei esse tópico antigo muito útil - em particular, os comentários de Bill Fisher e Jing Chen. Isso está muito próximo do que o @BinaryMuse está propondo, com a menor diferença de que o envio acontece no criador da ação.
Phillipwei
37

O Fluxxor tem um exemplo de comunicação assíncrona com uma API.

Esta postagem do blog fala sobre isso e foi destaque no blog da React.


Acho essa uma pergunta muito importante e difícil que ainda não foi respondida claramente, pois a sincronização do software front-end com o back-end ainda é um problema.

As solicitações de API devem ser feitas nos componentes JSX? Lojas? Outro lugar?

A execução de solicitações nas lojas significa que, se duas lojas precisarem dos mesmos dados para uma determinada ação, elas emitirão 2 requets semelhantes (a menos que você introduza dependências entre lojas, das quais eu realmente não gosto )

No meu caso, achei muito útil colocar as promessas de Q como carga útil de ações porque:

  • Minhas ações não precisam ser serializáveis ​​(não mantenho um log de eventos, não preciso do recurso de repetição de eventos da fonte de eventos)
  • Isso elimina a necessidade de ações / eventos diferentes (solicitação acionada / solicitação concluída / falha na solicitação) e precisa correspondê-los usando os IDs de correlação quando as solicitações simultâneas podem ser acionadas.
  • Permite que várias lojas ouçam a conclusão da mesma solicitação, sem introduzir nenhuma dependência entre as lojas (no entanto, pode ser melhor introduzir uma camada de armazenamento em cache?)

Ajax é MAU

Eu acho que o Ajax será cada vez menos usado no futuro próximo, porque é muito difícil argumentar. O caminho certo? Considerando os dispositivos como parte do sistema distribuído, não sei onde me deparei com essa ideia (talvez neste inspirador vídeo de Chris Granger ).

Pense nisso. Agora, para escalabilidade, usamos sistemas distribuídos com consistência eventual como mecanismos de armazenamento (porque não podemos vencer o teorema do CAP e muitas vezes queremos estar disponíveis). Esses sistemas não sincronizam através da pesquisa entre si (exceto talvez para operações de consenso?), Mas usam estruturas como CRDT e logs de eventos para tornar todos os membros do sistema distribuído eventualmente consistentes (os membros convergirão para os mesmos dados, com tempo suficiente) .

Agora pense no que é um dispositivo móvel ou um navegador. É apenas um membro do sistema distribuído que pode sofrer latência e particionamento de rede.(ou seja, você está usando seu smartphone no metrô)

Se pudermos criar partição de rede e bancos de dados tolerantes à velocidade da rede (quero dizer, ainda podemos executar operações de gravação em um nó isolado), provavelmente podemos criar softwares de front-end (móveis ou desktop) inspirados nesses conceitos, que funcionam bem com o modo offline suportado da caixa sem o aplicativo apresenta a indisponibilidade.

Acho que deveríamos nos inspirar em como os bancos de dados estão trabalhando para arquitetar nossos aplicativos de front-end. Uma coisa a observar é que esses aplicativos não executam solicitações POST e PUT e GET ajax para enviar dados uns aos outros, mas usam logs de eventos e CRDT para garantir consistência eventual.

Então, por que não fazer isso no frontend? Observe que o back-end já está se movendo nessa direção, com ferramentas como Kafka adotadas massivamente por grandes jogadores. De alguma forma, isso também está relacionado ao Event Sourcing / CQRS / DDD.

Confira estes artigos impressionantes dos autores Kafka para se convencer:

Talvez possamos começar enviando comandos para o servidor e recebendo um fluxo de eventos do servidor (por meio de websockets, por exemplo), em vez de disparar solicitações do Ajax.

Nunca me senti muito à vontade com os pedidos do Ajax. À medida que reagimos, os desenvolvedores tendem a ser programadores funcionais. Eu acho que é difícil argumentar sobre dados locais que deveriam ser sua "fonte de verdade" do seu aplicativo de front-end, enquanto a fonte real de verdade está realmente no banco de dados do servidor, e sua fonte "local" de verdade já pode estar desatualizada quando você o recebe e nunca convergirá para a fonte real do valor da verdade, a menos que você pressione algum botão de atualização coxo ... Isso é engenharia?

No entanto, ainda é um pouco difícil projetar isso por alguns motivos óbvios:

  • Seu cliente de celular / navegador possui recursos limitados e não pode necessariamente armazenar todos os dados localmente (portanto, às vezes, é necessário pesquisar com um conteúdo pesado de solicitação do ajax)
  • Seu cliente não deve ver todos os dados do sistema distribuído, portanto, de alguma forma, é necessário filtrar os eventos que recebe por motivos de segurança
Sebastien Lorber
fonte
3
Você pode fornecer um exemplo do uso de promessas Q com ações?
Matt Foxx Duncan
O @MattFoxxDuncan não tem certeza de que é uma boa idéia, pois torna o "log de eventos" não serializável e atualiza a loja de forma assíncrona nas ações que estão sendo acionadas, por isso há algumas desvantagens. reduzir o boilerplate. Com Fluxxor provavelmente você pode fazer algo assimthis.dispatch("LOAD_DATA", {dataPromise: yourPromiseHere});
Sebastien Lorber
Discordo completamente do seu argumento AJAX. Na verdade, era muito chato de ler. Você leu suas observações? Pense em lojas, jogos, aplicativos que ganham muito dinheiro - todos exigem chamadas de servidor API e AJAX. Olhe para o Firebase se você quiser "sem servidor" ou algo dessa natureza, mas o AJAX está aqui para dizer que espero que pelo menos mais ninguém concorde com sua lógica
TheBlackBenzKid 30/11
@TheBlackBenzKid Não estou dizendo que o Ajax desaparecerá totalmente durante o ano (e tenha certeza de que ainda estou criando sites com solicitações de ajax atualmente como CTO de uma startup), mas estou dizendo que provavelmente desaparecerá porque não é um protocolo bom o suficiente para lidar com a consistência eventual, o que exige streaming e não pesquisa, e a consistência eventual é o que permite que os aplicativos funcionem offline de maneira confiável (sim, você pode hackear algo com armazenamento local, mas terá capacidades offline limitadas, ou seu aplicativo é muito simples). O problema não está no cache, está invalidando esse cache.
Sebastien Lorber
@TheBlackBenzKid Os modelos por trás do Firebase, Meteor etc não são bons o suficiente. Você sabe como esses sistemas lidam com gravações simultâneas? última gravação-vitória em vez de consistência causal / estratégias de fusão? Você pode se dar ao luxo de substituir o trabalho do seu colega em um aplicativo quando ambos estiverem trabalhando em conexões não confiáveis? Observe também que esses sistemas tendem a combinar bastante as modelizações local e de servidor. Você conhece algum aplicativo colaborativo conhecido que seja significativamente complexo, que funcione perfeitamente offline, declarando ser um usuário satisfeito do Firebase? Eu não faço
Sebastien Lorber
20

Você pode solicitar dados nos criadores da ação ou nas lojas. O importante é não manipular a resposta diretamente, mas criar uma ação no retorno de chamada de erro / êxito. O tratamento da resposta diretamente na loja leva a um design mais frágil.

fisherwebdev
fonte
9
Você pode explicar isso com mais detalhes, por favor? Digamos que eu precise fazer o carregamento inicial de dados do servidor. Na visão do controlador, inicio uma ação INIT, e o Store inicia sua inicialização assíncrona, refletindo essa ação. Agora, eu aceitaria a idéia de que, quando a Store buscasse os dados, eles simplesmente emitiriam alterações, mas não iniciariam uma ação. A emissão de uma alteração após a inicialização informa às visualizações que eles podem obter os dados do armazenamento. Por que é necessário não emitir uma alteração após o carregamento bem-sucedido, mas iniciar outra ação ?! Obrigado
Jim-Y
Fisherwebdev, sobre lojas que pedem dados, ao fazer isso, não quebre o paradigma Flux, as duas únicas maneiras adequadas de pensar em solicitar dados é: 1. use uma classe de autoinicialização usando Actions para carregar dados 2 Vistas, novamente usando Ações para carregar dados
Yotam
4
Pedir dados não é o mesmo que receber dados. @ Jim-Y: você só deve emitir alterações uma vez que os dados na loja realmente mudaram. Yotam: Não, solicitar dados na loja não quebra o paradigma. Os dados devem ser recebidos apenas por meio de ações, para que todas as lojas possam ser informadas por novos dados inseridos no aplicativo. Portanto, podemos solicitar dados em uma loja, mas quando a resposta voltar, precisamos criar uma nova ação em vez de manipulá-la diretamente. Isso mantém o aplicativo flexível e resiliente ao desenvolvimento de novos recursos.
fisherwebdev
2

Eu tenho usado o exemplo do Binary Muse no exemplo do Fluxxor ajax . Aqui está o meu exemplo muito simples, usando a mesma abordagem.

Tenho uma loja de produtos simples , algumas ações do produto e o componente de visualização do controlador, que possui subcomponentes que respondem a alterações feitas na loja de produtos . Por exemplo produto-slider , produto-list e produto de busca componentes.

Cliente de produto falso

Aqui está o cliente falso que você pode substituir por chamar um endpoint real que retorne produtos.

var ProductClient = {

  load: function(success, failure) {
    setTimeout(function() {
      var ITEMS = require('../data/product-data.js');
      success(ITEMS);
    }, 1000);
  }    
};

module.exports = ProductClient;

Loja de produtos

Aqui está a loja do produto, obviamente essa é uma loja muito mínima.

var Fluxxor = require("fluxxor");

var store = Fluxxor.createStore({

  initialize: function(options) {

    this.productItems = [];

    this.bindActions(
      constants.LOAD_PRODUCTS_SUCCESS, this.onLoadSuccess,
      constants.LOAD_PRODUCTS_FAIL, this.onLoadFail
    );
  },

  onLoadSuccess: function(data) {    
    for(var i = 0; i < data.products.length; i++){
      this.productItems.push(data.products[i]);
    }    
    this.emit("change");
  },

  onLoadFail: function(error) {
    console.log(error);    
    this.emit("change");
  },    

  getState: function() {
    return {
      productItems: this.productItems
    };
  }
});

module.exports = store;

Agora, as ações do produto, que fazem a solicitação do AJAX e, com sucesso, acionam a ação LOAD_PRODUCTS_SUCCESS que retorna produtos para a loja.

Ações do produto

var ProductClient = require("../fake-clients/product-client");

var actions = {

  loadProducts: function() {

    ProductClient.load(function(products) {
      this.dispatch(constants.LOAD_PRODUCTS_SUCCESS, {products: products});
    }.bind(this), function(error) {
      this.dispatch(constants.LOAD_PRODUCTS_FAIL, {error: error});
    }.bind(this));
  }    

};

module.exports = actions;

Então chamando this.getFlux().actions.productActions.loadProducts() de qualquer componente que esteja ouvindo esta loja carregaria os produtos.

Você pode imaginar ações diferentes, que responderiam às interações do usuário como addProduct(id) removeProduct(id)etc ... seguindo o mesmo padrão.

Espero que o exemplo ajude um pouco, pois achei isso um pouco complicado de implementar, mas certamente ajudou a manter minhas lojas 100% síncronas.

svnm
fonte
2

Respondi a uma pergunta relacionada aqui: Como lidar com chamadas de API aninhadas no fluxo

As ações não devem ser coisas que causam uma mudança. Eles deveriam ser como um jornal que informa a aplicação de uma mudança no mundo exterior e, em seguida, o aplicativo responde a essas notícias. As lojas causam mudanças em si mesmas. Ações apenas os informam.

Bill Fisher, criador do Flux https://stackoverflow.com/a/26581808/4258088

Basicamente, o que você deve fazer é declarar por meio de ações quais dados você precisa. Se a loja for informada pela ação, deve decidir se precisa buscar alguns dados.

A loja deve ser responsável por acumular / buscar todos os dados necessários. É importante observar, porém, que depois que a loja solicitou os dados e obtém a resposta, ela deve disparar uma ação com os dados buscados, em oposição à loja que manipula / salva a resposta diretamente.

A lojas poderia ser algo como isto:

class DataStore {
  constructor() {
    this.data = [];

    this.bindListeners({
      handleDataNeeded: Action.DATA_NEEDED,
      handleNewData: Action.NEW_DATA
    });
  }

  handleDataNeeded(id) {
    if(neededDataNotThereYet){
      api.data.fetch(id, (err, res) => {
        //Code
        if(success){
          Action.newData(payLoad);
        }
      }
    }
  }

  handleNewData(data) {
    //code that saves data and emit change
  }
}
MoeSattler
fonte
0

Aqui está minha opinião sobre isso: http://www.thedreaming.org/2015/03/14/react-ajax/

Espero que ajude. :)

Jason Walton
fonte
8
voto negativo de acordo com as diretrizes. colocar respostas em sites externos torna este site menos útil e gera respostas de menor qualidade, diminuindo a utilidade do site. urls externos provavelmente também serão interrompidos no tempo. o downvote não diz nada sobre a utilidade do artigo, que por sinal é muito bom :)
oligofren
2
Boa publicação, mas adicionar um breve resumo dos prós / contras de cada abordagem fará com que você receba votos positivos. No SO, não precisamos clicar em um link para obter a essência da sua resposta.
Cory House