Wie wird beim Laden der Daten eine Ladeanzeige in der React Redux-App angezeigt? [geschlossen]


107

Ich bin neu bei React / Redux. Ich verwende eine Fetch-API-Middleware in der Redux-App, um die APIs zu verarbeiten. Es ist ( Redux-API-Middleware ). Ich denke, es ist der gute Weg, um asynchrone API-Aktionen zu verarbeiten. Aber ich finde einige Fälle, die nicht von mir selbst gelöst werden können.

Wie auf der Homepage ( Lifecycle ) angegeben, beginnt ein Abruf-API-Lebenszyklus mit dem Versenden einer CALL_API-Aktion und endet mit dem Versenden einer FSA-Aktion.

Mein erster Fall ist also das Ein- / Ausblenden eines Preloaders beim Abrufen von APIs. Die Middleware sendet zu Beginn eine FSA-Aktion und am Ende eine FSA-Aktion. Beide Aktionen werden von Reduzierern empfangen, die nur eine normale Datenverarbeitung durchführen sollten. Keine UI-Operationen, keine Operationen mehr. Vielleicht sollte ich den Verarbeitungsstatus im Status speichern und ihn dann beim Aktualisieren des Speichers rendern.

Aber wie geht das? Ein Reaktionskomponentenfluss über die gesamte Seite? Was passiert mit der Aktualisierung des Geschäfts aufgrund anderer Aktionen? Ich meine, sie sind eher Ereignisse als Staaten!

Was soll ich tun, wenn ich den nativen Bestätigungsdialog oder den Warndialog in Redux / React-Apps verwenden muss? Wo sollen sie platziert werden, Maßnahmen oder Reduzierungen?

Die besten Wünsche! Wunsch für eine Antwort.


1
Die letzte Bearbeitung dieser Frage wurde rückgängig gemacht, da der gesamte Punkt der Frage und der Antworten unten geändert wurde.
Gregg B

Ein Ereignis ist die Zustandsänderung!
大师 应用 架构 模式 大师 10.

Schauen Sie sich questrar an. github.com/orar/questrar
Orar

Antworten:


152

Ich meine, sie sind eher Ereignisse als Staaten!

Das würde ich nicht sagen. Ich denke, Ladeindikatoren sind ein großartiger Fall der Benutzeroberfläche, der leicht als Funktion des Zustands beschrieben werden kann: in diesem Fall einer booleschen Variablen. Obwohl diese Antwort richtig ist, möchte ich einen Code bereitstellen, der dazu passt.

Im asyncBeispiel in Redux Repo aktualisiert derisFetching Reduzierer ein Feld mit dem Namen :

case REQUEST_POSTS:
  return Object.assign({}, state, {
    isFetching: true,
    didInvalidate: false
  })
case RECEIVE_POSTS:
  return Object.assign({}, state, {
    isFetching: false,
    didInvalidate: false,
    items: action.posts,
    lastUpdated: action.receivedAt

Die Komponente verwendet connect()React Redux, um den Status des Geschäfts zu abonnieren, und gibt isFetchingals Teil des mapStateToProps()Rückgabewerts zurück, sodass er in den Requisiten der verbundenen Komponente verfügbar ist:

function mapStateToProps(state) {
  const { selectedReddit, postsByReddit } = state
  const {
    isFetching,
    lastUpdated,
    items: posts
  } = postsByReddit[selectedReddit] || {
    isFetching: true,
    items: []
  }

  return {
    selectedReddit,
    posts,
    isFetching,
    lastUpdated
  }
}

Schließlich werden die Komponente Anwendungen isFetchingprop in der render()Funktion eines „Loading ...“ Label zu machen (die möglicherweise ein Spinner statt sein könnte):

{isEmpty
  ? (isFetching ? <h2>Loading...</h2> : <h2>Empty.</h2>)
  : <div style={{ opacity: isFetching ? 0.5 : 1 }}>
      <Posts posts={posts} />
    </div>
}

Was soll ich tun, wenn ich den nativen Bestätigungsdialog oder den Warndialog in Redux / React-Apps verwenden muss? Wo sollen sie platziert werden, Maßnahmen oder Reduzierungen?

Nebenwirkungen (und das Anzeigen eines Dialogs ist mit Sicherheit eine Nebenwirkung) gehören nicht zu den Reduzierern. Stellen Sie sich Reduzierer als passive „Staatsbauer“ vor. Sie "tun" nicht wirklich Dinge.

Wenn Sie eine Warnung anzeigen möchten, tun Sie dies entweder von einer Komponente aus, bevor Sie eine Aktion auslösen, oder von einem Aktionsersteller. Zum Zeitpunkt des Versands einer Aktion ist es zu spät, um als Reaktion darauf Nebenwirkungen auszuführen.

Für jede Regel gibt es eine Ausnahme. Manchmal ist Ihre Nebenwirkungslogik so kompliziert, dass Sie sie entweder an bestimmte Aktionstypen oder an bestimmte Reduzierungen koppeln möchten . In diesem Fall sehen Sie sich fortgeschrittene Projekte wie Redux Saga und Redux Loop an . Tun Sie dies nur, wenn Sie mit Vanille Redux vertraut sind und ein echtes Problem mit vereinzelten Nebenwirkungen haben, die Sie besser handhaben möchten.


16
Was ist, wenn ich mehrere Abrufe habe? Dann würde eine Variable allerdings nicht ausreichen.
Philk

1
@philk Wenn Sie mehrere Abrufe haben, können Sie sie mit gruppieren Promise.all zu einem einzigen Versprechen zusammenfassen und dann eine einzige Aktion für alle Abrufe auslösen. Oder Sie müssen mehrere isFetchingVariablen in Ihrem Status verwalten.
Sebastien Lorber

2
Bitte schauen Sie sich das Beispiel, auf das ich verweise, genau an. Es gibt mehr als eineisFetching Flagge. Es wird für jede Gruppe von Objekten festgelegt, die abgerufen werden. Sie können die Reduziererzusammensetzung verwenden, um dies zu implementieren.
Dan Abramov

3
Beachten Sie, dass das Ladeschild an Ort und Stelle RECEIVE_POSTSbleibt , wenn die Anforderung fehlschlägt und niemals ausgelöst wird, es sei denn, Sie haben eine Zeitüberschreitung zum error loadingAnzeigen einer Nachricht erstellt.
James111

2
@TomiS - Ich habe alle meine isFetching-Eigenschaften explizit auf die schwarze Liste gesetzt, unabhängig von der von mir verwendeten Redux-Persistenz.
duhseekoh

22

Tolle Antwort Dan Abramov! Ich möchte nur hinzufügen, dass ich mehr oder weniger genau das in einer meiner Apps getan habe (isFetching als Booleschen Wert beibehalten) und es schließlich zu einer Ganzzahl machen musste (die letztendlich als Anzahl der ausstehenden Anforderungen gelesen wird), um mehrere gleichzeitig zu unterstützen Anfragen.

mit boolean:

Anfrage 1 startet -> Spinner ein -> Anfrage 2 startet -> Anfrage 1 endet -> Spinner aus -> Anfrage 2 endet

mit Ganzzahl:

Anfrage 1 startet -> Spinner ein -> Anfrage 2 startet -> Anfrage 1 endet -> Anfrage 2 endet -> Spinner aus

case REQUEST_POSTS:
  return Object.assign({}, state, {
    isFetching: state.isFetching + 1,
    didInvalidate: false
  })
case RECEIVE_POSTS:
  return Object.assign({}, state, {
    isFetching: state.isFetching - 1,
    didInvalidate: false,
    items: action.posts,
    lastUpdated: action.receivedAt

2
Das ist vernünftig. Meistens möchten Sie jedoch zusätzlich zum Flag auch einige Daten speichern, die Sie abrufen. Zu diesem Zeitpunkt benötigen Sie mehr als ein Objekt mit isFetchingFlag. Wenn Sie sich das Beispiel, auf das ich verlinkt habe, genau ansehen, werden Sie feststellen, dass es nicht ein Objekt mit, isFetchedsondern viele gibt: eines pro Subreddit (was in diesem Beispiel abgerufen wird).
Dan Abramov

2
Oh. Ja, das habe ich nicht bemerkt. In meinem Fall habe ich jedoch einen globalen isFetching-Eintrag im Status und einen Cache-Eintrag, in dem die abgerufenen Daten gespeichert sind, und für meine Zwecke ist es mir nur wirklich wichtig, dass eine Netzwerkaktivität stattfindet, es spielt keine Rolle, wofür
Nuno Campos

4
Ja! Dies hängt davon ab, ob Sie den Abrufindikator an einer oder mehreren Stellen in der Benutzeroberfläche anzeigen möchten. Tatsächlich können Sie die beiden Ansätze kombinieren und sowohl einen globalen fetchCounterFortschrittsbalken für einige Fortschrittsbalken am oberen Bildschirmrand als auch mehrere spezifische isFetchingFlags für Listen und Seiten haben.
Dan Abramov

Wenn ich POST-Anforderungen in mehr als einer Datei habe, wie würde ich den Status von isFetching festlegen, um den aktuellen Status zu verfolgen?
user989988

13

Ich möchte etwas hinzufügen. Im Beispiel der realen Welt wird ein Feld isFetchingim Geschäft verwendet, um darzustellen, wann eine Sammlung von Elementen abgerufen wird. Jede Sammlung wird auf einen paginationReduzierer verallgemeinert, der mit Ihren Komponenten verbunden werden kann, um den Status zu verfolgen und anzuzeigen, ob eine Sammlung geladen wird.

Mir ist passiert, dass ich Details für eine bestimmte Entität abrufen wollte, die nicht in das Paginierungsmuster passt. Ich wollte einen Status haben, der darstellt, ob die Details vom Server abgerufen werden, aber ich wollte auch keinen Reduzierer dafür haben.

Um dies zu lösen, habe ich einen weiteren generischen Reduzierer namens hinzugefügt fetching. Es funktioniert in ähnlicher Weise wie die Paginierung Minderer und es liegt in der Verantwortung nur zu beobachten , eine Reihe von Aktionen und neuen Zustand mit Paaren zu erzeugen [entity, isFetching]. Auf diese Weise kann connectder Reduzierer auf jede Komponente zugreifen und feststellen, ob die App derzeit Daten nicht nur für eine Sammlung, sondern für eine bestimmte Entität abruft.


2
Danke für die Antwort! Der Umgang mit dem Laden einzelner Artikel und deren Status wird selten diskutiert!
Gilad Peleg

Wenn ich eine Komponente habe, die von der Aktion einer anderen abhängt, ist ein schneller und schmutziger Ausweg in Ihren mapStateToProps. Kombinieren Sie sie wie folgt: isFetching: posts.isFetching || comment.isFetching - Jetzt können Sie die Benutzerinteraktion für beide Komponenten blockieren, wenn eine der beiden aktualisiert wird.
Philip Murphy

5

Ich bin auf diese Frage bis jetzt nicht gestoßen, aber da keine Antwort akzeptiert wird, werde ich meinen Hut hineinwerfen. Ich habe genau für diesen Job ein Tool geschrieben: React-Loader-Factory . Es ist etwas mehr los als Abramovs Lösung, aber es ist modularer und praktischer, da ich nach dem Schreiben nicht nachdenken musste.

Es gibt vier große Stücke:

  • Werksmuster: Auf diese Weise können Sie schnell dieselbe Funktion aufrufen, um festzulegen, welche Zustände "Laden" für Ihre Komponente bedeuten und welche Aktionen ausgelöst werden sollen. (Dies setzt voraus, dass die Komponente für das Starten der Aktionen verantwortlich ist, auf die sie wartet.)const loaderWrapper = loaderFactory(actionsList, monitoredStates);
  • Wrapper: Die Komponente, die die Factory produziert, ist eine Komponente "höherer Ordnung" (wie sie connect()in Redux zurückgegeben wird), sodass Sie sie einfach an Ihre vorhandenen Materialien anschrauben können.const LoadingChild = loaderWrapper(ChildComponent);
  • Aktion / Reduzierer-Interaktion: Der Wrapper prüft, ob ein Reduzierer, an den er angeschlossen ist, Schlüsselwörter enthält, die angeben, dass er nicht an die Komponente weitergeleitet werden soll, die Daten benötigt. Es wird erwartet, dass die vom Wrapper ausgelösten Aktionen die zugehörigen Schlüsselwörter erzeugen (wie z. B. die Redux-API-Middleware-Versendung ACTION_SUCCESSund ACTION_REQUEST). (Sie können Aktionen auch an eine andere Stelle senden und natürlich nur vom Wrapper aus überwachen, wenn Sie möchten.)
  • Throbber: Die Komponente, die angezeigt werden soll, während die Daten, von denen Ihre Komponente abhängt, nicht bereit sind. Ich habe dort ein kleines Div hinzugefügt, damit Sie es testen können, ohne es aufbauen zu müssen.

Das Modul selbst ist unabhängig von Redux-API-Middleware, aber damit verwende ich es. Hier ist ein Beispielcode aus der README-Datei:

Eine Komponente mit einem Loader, der sie umschließt:

import React from 'react';
import { myAsyncAction } from '../actions';
import loaderFactory from 'react-loader-factory';
import ChildComponent from './ChildComponent';

const actionsList = [myAsyncAction()];
const monitoredStates = ['ASYNC_REQUEST'];
const loaderWrapper = loaderFactory(actionsList, monitoredStates);

const LoadingChild = loaderWrapper(ChildComponent);

const containingComponent = props => {
  // Do whatever you need to do with your usual containing component 

  const childProps = { someProps: 'props' };

  return <LoadingChild { ...childProps } />;
}

Ein Reduzierer, den der Loader überwachen kann (obwohl Sie ihn auch anders verkabeln können , wenn Sie möchten):

export function activeRequests(state = [], action) {
  const newState = state.slice();

  // regex that tests for an API action string ending with _REQUEST 
  const reqReg = new RegExp(/^[A-Z]+\_REQUEST$/g);
  // regex that tests for a API action string ending with _SUCCESS 
  const sucReg = new RegExp(/^[A-Z]+\_SUCCESS$/g);

  // if a _REQUEST comes in, add it to the activeRequests list 
  if (reqReg.test(action.type)) {
    newState.push(action.type);
  }

  // if a _SUCCESS comes in, delete its corresponding _REQUEST 
  if (sucReg.test(action.type)) {
    const reqType = action.type.split('_')[0].concat('_REQUEST');
    const deleteInd = state.indexOf(reqType);

    if (deleteInd !== -1) {
      newState.splice(deleteInd, 1);
    }
  }

  return newState;
}

Ich gehe davon aus, dass ich in naher Zukunft dem Modul Dinge wie Timeout und Fehler hinzufügen werde, aber das Muster wird nicht sehr unterschiedlich sein.


Die kurze Antwort auf Ihre Frage lautet:

  1. Binden Sie das Rendern mit dem Rendern von Code - verwenden Sie einen Wrapper um die Komponente, die Sie rendern möchten, mit den Daten wie den oben gezeigten.
  2. Fügen Sie einen Reduzierer hinzu, der den Status von Anfragen rund um die App, die Sie interessieren könnten, leicht verdaulich macht, sodass Sie nicht zu lange darüber nachdenken müssen, was gerade passiert.
  3. Ereignisse und Zustand sind nicht wirklich unterschiedlich.
  4. Der Rest Ihrer Intuitionen scheint mir richtig zu sein.

4

Bin ich der einzige, der denkt, dass Ladeindikatoren nicht in einen Redux-Store gehören? Ich meine, ich glaube nicht, dass es per se Teil des Status einer Anwendung ist.

Jetzt arbeite ich mit Angular2 und habe einen "Ladedienst", der verschiedene Ladeindikatoren über RxJS BehaviourSubjects anzeigt. Ich denke, der Mechanismus ist der gleiche, ich speichere die Informationen einfach nicht in Redux.

Benutzer des LoadingService abonnieren nur die Ereignisse, die sie abhören möchten.

Meine Redux-Aktionsersteller rufen den LoadingService auf, wenn sich etwas ändern muss. UX-Komponenten abonnieren die exponierten Observablen ...


Aus diesem Grund gefällt mir die Idee des Speichers, in dem alle Aktionen abgefragt werden können (ngrx und Redux-Logik). Service ist nicht funktionsfähig, Redux-Logik - funktional. Schöne Lektüre
Srghma

20
Hallo, ich habe mehr als ein Jahr später noch einmal nachgesehen, nur um zu sagen, dass ich mich sehr geirrt habe. Natürlich gehört der UX-Status zum Anwendungsstatus. Wie dumm könnte ich sein?
Spock

3

Sie können Ihren Filialen Änderungslistener hinzufügen, indem Sie entweder connect()React Redux oder die Low-Level- store.subscribe()Methode verwenden. Sie sollten die Ladeanzeige in Ihrem Geschäft haben, die der Handler für Geschäftsänderungen dann überprüfen und den Komponentenstatus aktualisieren kann. Die Komponente rendert dann den Preloader bei Bedarf basierend auf dem Status.

alertund confirmsollte kein Problem sein. Sie blockieren und der Alarm nimmt nicht einmal Eingaben vom Benutzer entgegen. Mit confirmkönnen Sie den Status basierend auf dem Klick des Benutzers festlegen, wenn die Benutzerauswahl das Rendern von Komponenten beeinflussen soll. Wenn nicht, können Sie die Auswahl als Komponentenelementvariable für die spätere Verwendung speichern.


Über den Alarm- / Bestätigungscode, wo sollen sie platziert werden, Aktionen oder Reduzierungen?
17 应用 架构 模式

Kommt darauf an, was Sie mit ihnen machen wollen, aber ehrlich gesagt würde ich sie in den meisten Fällen in Komponentencode einfügen, da sie Teil der Benutzeroberfläche sind, nicht der Datenschicht.
Miloš Rašić

Einige UI-Komponenten lösen ein Ereignis (Ereignis mit Statusänderung) anstelle des Status selbst aus. Zum Beispiel eine Animation, die den Preloader ein- / ausblendet. Wie verarbeitest du sie?
17 应用 架构 模式 大师 17.

Wenn Sie eine nicht reagierende Komponente in Ihrer Reaktions-App verwenden möchten, besteht die allgemein verwendete Lösung darin, eine Wrapper-Reaktionskomponente zu erstellen und dann mithilfe ihrer Lebenszyklusmethoden eine Instanz der nicht reagierenden Komponente zu initialisieren, zu aktualisieren und zu zerstören. Die meisten dieser Komponenten verwenden Platzhalterelemente im DOM zum Initialisieren, und Sie rendern diese in der Rendermethode der Reaktionskomponente. Weitere
Miloš Rašić

Ich habe einen Fall: Ein Benachrichtigungsbereich in der rechten oberen Ecke, der eine Benachrichtigungsnachricht enthält. Jede Nachricht wird angezeigt und verschwindet nach 5 Sekunden. Diese Komponente befindet sich außerhalb der Webansicht, die von der nativen Host-App bereitgestellt wird. Es bietet einige js-Schnittstelle wie addNofication(message). Ein weiterer Fall sind die Preloader, die ebenfalls von der nativen Host-App bereitgestellt und von ihrer Javascript-API ausgelöst werden. Ich füge einen Wrapper für diese API in componentDidUpdateeiner React-Komponente hinzu. Wie entwerfe ich die Requisiten oder den Status dieser Komponente?
18 应用 架构 模式 大师 18.

3

Wir haben drei Arten von Benachrichtigungen in unserer App, die alle als Aspekte konzipiert sind:

  1. Ladeanzeige (modal oder nicht modal basierend auf Prop)
  2. Fehler-Popup (modal)
  3. Benachrichtigungs-Snackbar (nicht modal, selbstschließend)

Alle drei befinden sich auf der obersten Ebene unserer App (Main) und werden über Redux verkabelt, wie im folgenden Codeausschnitt gezeigt. Diese Requisiten steuern die Anzeige ihrer entsprechenden Aspekte.

Ich habe einen Proxy entworfen, der alle unsere API-Aufrufe verarbeitet. Daher werden alle isFetching- und (API-) Fehler mit actionCreators vermittelt, die ich in den Proxy importiere. (Nebenbei benutze ich auch Webpack, um einen Mock des Backing-Service für Entwickler einzufügen, damit wir ohne Serverabhängigkeiten arbeiten können.)

Jeder andere Ort in der App, der irgendeine Art von Benachrichtigung bereitstellen muss, importiert einfach die entsprechende Aktion. Snackbar & Error haben Parameter für die Anzeige von Nachrichten.

@connect(
// map state to props
state => ({
    isFetching      :state.main.get('isFetching'),   // ProgressIndicator
    notification    :state.main.get('notification'), // Snackbar
    error           :state.main.get('error')         // ErrorPopup
}),
// mapDispatchToProps
(dispatch) => { return {
    actions: bindActionCreators(actionCreators, dispatch)
}}

) Export Standardklasse Main erweitert React.Component {


Ich arbeite an einem ähnlichen Setup mit dem Anzeigen eines Laders / Benachrichtigungen. Ich habe Probleme; Hätten Sie einen Überblick oder ein Beispiel dafür, wie Sie diese Aufgaben erfüllen?
Aymen

2

Ich speichere die URLs wie ::

isFetching: {
    /api/posts/1: true,
    api/posts/3: false,
    api/search?q=322: true,
}

Und dann habe ich einen gespeicherten Selektor (über Reselect).

const getIsFetching = createSelector(
    state => state.isFetching,
    items => items => Object.keys(items).filter(item => items[item] === true).length > 0 ? true : false
);

Um die URL im Falle eines POST eindeutig zu machen, übergebe ich eine Variable als Abfrage.

Und wo ich einen Indikator anzeigen möchte, verwende ich einfach die Variable getFetchCount


1
Sie können übrigens Object.keys(items).filter(item => items[item] === true).length > 0 ? true : falsedurch ersetzen Object.keys(items).every(item => items[item]).
Alexandre Annic

1
Ich denke, Sie meinten somestattdessen every, aber ja, zu viele nicht benötigte Vergleiche in der ersten vorgeschlagenen Lösung. Object.entries(items).some(([url, fetching]) => fetching);
Rafael Porras Lucena
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.