I negozi di flusso o le azioni (o entrambi) dovrebbero toccare i servizi esterni?


122

Se i negozi mantengono il proprio stato e hanno la capacità di chiamare i servizi di archiviazione di rete e dati nel farlo ... nel qual caso le azioni sono solo stupidi passanti di messaggi,

-O-

... gli archivi dovrebbero essere destinatari stupidi di dati immutabili dalle azioni (e le azioni dovrebbero essere quelle che recuperano / inviano dati tra fonti esterne? L'archivio in questa istanza fungerebbe da modello di visualizzazione e sarebbe in grado di aggregare / filtrare dati prima di impostare la propria base di stato sui dati immutabili che sono stati alimentati dall'azione.

Mi sembra che dovrebbe essere l'uno o l'altro (piuttosto che un mix di entrambi). In tal caso, perché uno è preferito / consigliato rispetto all'altro?


2
Questo post potrebbe aiutare code-experience.com/…
Markus-ipse

Per coloro che valutano le varie implementazioni del modello di flusso, consiglio vivamente di dare un'occhiata a Redux github.com/rackt/redux Gli store sono implementati come funzioni pure che accettano lo stato corrente ed emettono una nuova versione di quello stato. Poiché sono funzioni pure, la questione se possono o meno chiamare i servizi di rete e di archiviazione ti sfugge di mano: non possono.
plaxdan

Risposte:


151

Ho visto il modello di flusso implementato in entrambi i modi, e dopo aver fatto entrambe le cose (inizialmente seguendo il primo approccio), credo che gli archivi dovrebbero essere destinatari stupidi dei dati dalle azioni e che l'elaborazione asincrona delle scritture dovrebbe vivere nel creatori di azioni. (Le letture asincrone possono essere gestite in modo diverso .) Nella mia esperienza, questo ha alcuni vantaggi, in ordine di importanza:

  1. I tuoi negozi diventano completamente sincroni. Questo rende la logica del tuo negozio molto più facile da seguire e molto facile da testare: basta creare un'istanza di un negozio con un determinato stato, inviargli un'azione e controllare se lo stato è cambiato come previsto. Inoltre, uno dei concetti fondamentali nel flusso è quello di prevenire dispacciamenti a cascata e di impedire più invii contemporaneamente; questo è molto difficile da fare quando i tuoi negozi eseguono l'elaborazione asincrona.

  2. Tutti gli invii di azioni vengono eseguiti dai creatori di azioni. Se gestisci operazioni asincrone nei tuoi negozi e desideri mantenere i gestori delle azioni dei tuoi negozi sincroni (e dovresti per ottenere le garanzie di flusso singolo di spedizione), i tuoi negozi dovranno attivare ulteriori azioni SUCCESSO e FAIL in risposta alle azioni asincrone in lavorazione. Mettere questi dispacci nei creatori di azioni invece aiuta a separare i lavori dei creatori di azioni e dei negozi; inoltre, non è necessario scavare nella logica del negozio per capire da dove vengono inviate le azioni. Una tipica azione asincrona in questo caso potrebbe assomigliare a questa (cambia la sintassi delle dispatchchiamate in base al sapore del flusso che stai utilizzando):

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

    La logica che potrebbe altrimenti essere duplicata in varie azioni dovrebbe essere estratta in un modulo separato; in questo esempio, sarebbe quel modulo SomeDataAccessLayer, che gestisce l'esecuzione della richiesta Ajax effettiva.

  3. Hai bisogno di meno creatori di azioni. Questo non è un grosso problema, ma è bello averlo. Come accennato in # 2, se i tuoi negozi hanno una gestione della spedizione di azioni sincrone (e dovrebbero), dovrai attivare azioni extra per gestire i risultati delle operazioni asincrone. L'esecuzione dei dispacci nei creatori di azioni significa che un singolo creatore di azioni può inviare tutti e tre i tipi di azione gestendo il risultato dell'accesso asincrono ai dati stesso.


15
Penso che ciò che origina la chiamata api web (creatore di azioni o negozio) sia meno importante del fatto che il callback di successo / errore dovrebbe creare un'azione. Quindi il flusso di dati è quindi sempre: azione -> dispatcher -> negozi -> visualizzazioni.
fisherwebdev

1
Mettere la logica di richiesta effettiva all'interno di un modulo API sarebbe migliore / più facile da testare? Quindi il tuo modulo API potrebbe semplicemente restituire una promessa da cui spedisci. Il creatore dell'azione si limita a inviare in base a risoluzione / errore dopo aver inviato un'azione iniziale "in sospeso". La domanda che rimane è come il componente ascolta questi "eventi" poiché non sono sicuro che lo stato della richiesta debba mappare per memorizzare lo stato.
backdesk

@backdesk Questo è esattamente quello che faccio nell'esempio sopra: inviare un'azione iniziale in sospeso ( "SOME_ACTION"), utilizzare un'API per effettuare una richiesta ( SomeDataAccessLayer.doSomething(userId)) che restituisce una promessa e, nelle due .thenfunzioni, inviare azioni aggiuntive. Lo stato della richiesta può essere (più o meno) mappato per memorizzare lo stato se l'applicazione deve conoscere lo stato dello stato. Come questa mappa dipende dall'app (ad esempio, forse ogni commento ha uno stato di errore individuale, come Facebook, o forse c'è un componente di errore globale)
Michelle Tilley

@MichelleTilley "uno dei concetti fondamentali in corso è prevenire le spedizioni a cascata e impedire più invii contemporaneamente; questo è molto difficile da fare quando i tuoi negozi eseguono l'elaborazione asincrona." Questo è un punto chiave per me. Ben detto.

51

Ho twittato questa domanda agli sviluppatori di Facebook e la risposta che ho ricevuto da Bill Fisher è stata:

Quando rispondo all'interazione di un utente con l'interfaccia utente, effettuerei la chiamata asincrona nei metodi del creatore di azioni.

Ma quando hai un ticker o un altro driver non umano, una chiamata dal negozio funziona meglio.

La cosa importante è creare un'azione nel callback errore / successo in modo che i dati abbiano sempre origine con le azioni


Anche se questo ha senso, qualche idea del perché a call from store works better when action triggers from non-human driver ?
SharpCoder

@SharpCoder Immagino che se hai un live-ticker o qualcosa di simile, non hai davvero bisogno di lanciare un'azione e quando lo fai dal negozio, probabilmente devi scrivere meno codice, poiché il negozio può accedere immediatamente allo stato ed emette un cambiamento.
Florian Wendelborn

8

I negozi dovrebbero fare tutto, compreso il recupero dei dati e segnalare ai componenti che i dati del negozio sono stati aggiornati. Perché? Perché le azioni possono quindi essere leggere, usa e getta e sostituibili senza influenzare comportamenti importanti. Tutti i comportamenti e le funzionalità importanti si verificano nel negozio. Ciò impedisce anche la duplicazione di comportamenti che altrimenti verrebbero copiati in due azioni molto simili ma differenti. I negozi sono la tua unica fonte di (gestione della) verità.

In ogni implementazione di Flux che ho visto, le azioni sono fondamentalmente stringhe di eventi trasformate in oggetti, come tradizionalmente avresti un evento chiamato "anchor: clicked" ma in Flux sarebbe definito come AnchorActions.Clicked. Sono anche così "stupidi" che la maggior parte delle implementazioni ha oggetti Dispatcher separati per inviare effettivamente gli eventi ai negozi che stanno ascoltando.

Personalmente mi piace l'implementazione di Flux di Reflux in cui non ci sono oggetti Dispatcher separati e gli oggetti Action eseguono il dispacciamento da soli.


modifica: Flux di Facebook viene effettivamente recuperato in "creatori di azioni", quindi usano azioni intelligenti. Preparano anche il carico utile utilizzando i negozi:

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

La richiamata al completamento attiverebbe quindi una nuova azione questa volta con i dati recuperati come payload:

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

Quindi immagino che sia la soluzione migliore.


Cos'è questa implementazione di Reflux? Non ne ho sentito parlare. La tua risposta è interessante. Vuoi dire che l'implementazione del tuo negozio dovrebbe avere la logica per eseguire chiamate API e così via? Pensavo che i negozi dovessero solo ricevere dati e aggiornare i loro valori. Filtrano in base ad azioni specifiche e aggiornano alcuni attributi dei loro negozi.
Jeremy D

Reflux è una leggera variazione del Flux di Facebook: github.com/spoike/refluxjs I negozi gestiscono l'intero dominio "Modello" della tua applicazione, contro Azioni / Dispatcher che si limitano a cucire e incollare le cose insieme.
Rygu

1
Quindi ci ho pensato ancora un po 'e ho (quasi) risposto alla mia domanda. L'avrei aggiunto come risposta qui (per consentire agli altri di votare) ma a quanto pare sono troppo povero di karma a Stackoverflow per essere in grado di pubblicare una risposta ancora. Quindi ecco un link: groups.google.com/d/msg/reactjs/PpsvVPvhBbc/BZoG-bFeOwoJ
plaxdan

Grazie per il collegamento al gruppo di Google, sembra davvero informativo. Sono anche più fan di tutto ciò che passa attraverso il dispatcher e una logica davvero semplice nel negozio, in pratica, l'aggiornamento dei dati è tutto. @Rygu controllerò il reflusso.
Jeremy D

Ho modificato la mia risposta con una visualizzazione alternativa. Sembra che entrambe le soluzioni siano possibili. Quasi sicuramente sceglierei la soluzione di Facebook rispetto ad altre.
Rygu

3

Fornirò un argomento a favore delle azioni "stupide".

Assegnando la responsabilità della raccolta dei dati delle viste nelle tue azioni, abbini le tue azioni ai requisiti dei dati delle tue viste.

Al contrario, le azioni generiche, che descrivono in modo dichiarativo l' intento dell'utente o una transizione di stato nell'applicazione, consentono a qualsiasi Store che risponde a tale azione di trasformare l'intento in uno stato su misura per le visualizzazioni sottoscritte.

Questo si presta a negozi più numerosi, ma più piccoli e specializzati. Sostengo questo stile perché

  • questo ti offre una maggiore flessibilità nel modo in cui le visualizzazioni consumano i dati dello Store
  • I negozi "intelligenti", specializzati per le visualizzazioni che li consumano, saranno più piccoli e meno accoppiati per le app complesse, rispetto alle azioni "intelligenti", da cui dipendono potenzialmente molte visualizzazioni

Lo scopo di un negozio è fornire dati alle visualizzazioni. Il nome "Azione" mi suggerisce che il suo scopo è descrivere un cambiamento nella mia applicazione.

Supponiamo di dover aggiungere un widget a una visualizzazione Dashboard esistente, che mostra alcuni nuovi fantasiosi dati aggregati che il tuo team di backend ha appena implementato.

Con le azioni "intelligenti", potrebbe essere necessario modificare l'azione "aggiorna dashboard" per utilizzare la nuova API. Tuttavia, "Aggiornare il dashboard" in senso astratto non è cambiato. I requisiti in materia di dati delle tue opinioni sono ciò che è cambiato.

Con le azioni "stupide", potresti aggiungere un nuovo Store per il nuovo widget da consumare e configurarlo in modo che quando riceve il tipo di azione "aggiornamento dashboard", invii una richiesta per i nuovi dati e lo esponga a il nuovo widget una volta pronto. Per me ha senso che quando il livello di visualizzazione necessita di più o diversi dati, le cose che modifico sono le fonti di quei dati: Archivi.


2

La demo del router-flusso-reazione di gaeron ha una bella variazione di utilità dell'approccio "corretto".

Un ActionCreator genera una promessa da un servizio API esterno, quindi trasmette la promessa e tre costanti di azione a una dispatchAsyncfunzione in un proxy / Dispatcher esteso. dispatchAsyncinvierà sempre la prima azione, ad esempio "GET_EXTERNAL_DATA" e una volta restituita la promessa, invierà "GET_EXTERNAL_DATA_SUCCESS" o "GET_EXTERNAL_DATA_ERROR".


1

Se vuoi un giorno avere un ambiente di sviluppo paragonabile a quello che vedi nel famoso video di Bret Victor Inventing on Principle , dovresti piuttosto usare archivi stupidi che sono solo una proiezione di azioni / eventi all'interno di una struttura di dati, senza alcun effetto collaterale. Sarebbe anche utile se i tuoi negozi fossero effettivamente membri della stessa struttura dati globale immutabile, come in Redux .

Ulteriori spiegazioni qui: https://stackoverflow.com/a/31388262/82609

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.