2016-06-10 5 views
34

Wir haben eine Aktion, die ein Objekt asynchron abruft, nennen wir es getPostDetails, die einen Parameter von welchem ​​Post abholt, um eine ID. Dem Benutzer wird eine Liste mit Posts angezeigt, auf die Sie klicken können, um einige Details zu erhalten.Wie vermeidet man die Bedingungen beim Abrufen von Daten mit Redux?

Wenn ein Benutzer auf "Post # 1" klickt, versenden wir eine GET_POST Aktion, die in etwa so aussieht.

const getPostDetails = (id) => ({ 
    type: c.GET_POST_DETAILS, 
    promise: (http) => http.get(`http://example.com/posts/#${id}`), 
    returnKey: 'facebookData' 
}) 

Dies wird durch eine Middleware aufgenommen, die einen Erfolg Handler das Versprechen fügt hinzu, die eine Aktion wie GET_POST__OK mit dem entserialisierten JSON-Objekt aufruft. Der Reduzierer sieht dieses Objekt und wendet es auf ein Geschäft an. Ein typischer __OK Reducer sieht so aus.

[c.GET_ALL__OK]: (state, response) => assign(state, { 
    currentPost: response.postDetails 
}) 

später auf der ganzen Linie haben wir eine Komponente, die bei currentPost und zeigt die Details für die aktuelle Post sieht.

Wir haben jedoch eine Race-Bedingung. Wenn ein Benutzer zwei GET_POST_DETAILS Aktionen hintereinander einsendet, gibt es keine Garantie, welche Reihenfolge wir erhalten die __OK Aktionen in, wenn die zweite HTTP-Anfrage vor der ersten endet, wird der -Status falsch werden.

Action     => Result 
    --------------------------------------------------------------------------------- 
|T| User Clicks Post #1  => GET_POST for #1 dispatched => Http Request #1 pending 
|i| User Clicks Post #2  => GET_POST for #2 dispatched => Http Request #2 pending 
|m| Http Request #2 Resolves => Results for #2 added to state 
|e| Http Request #1 Resolves => Results for #1 added to state 
V 

Wie können wir sicherstellen, dass das letzte Element, auf das der Benutzer geklickt hat, immer Vorrang hat?

Antwort

71

Das Problem liegt an der suboptimalen Zustandsorganisation.

In einer Redux-App sind Zustandstasten wie currentPost normalerweise ein Anti-Pattern. Wenn Sie den Status jedes Mal zurücksetzen müssen, wenn Sie zu einer anderen Seite navigieren, haben Sie einen der folgenden Werte verloren: main benefits of Redux (or Flux): Zwischenspeichern. Sie können beispielsweise nicht mehr sofort zurück navigieren, wenn eine Navigation den Status zurücksetzt und die Daten erneut abruft.

Eine bessere Möglichkeit, diese Informationen zu speichern wäre zu trennen postsById und currentPostId:

{ 
    currentPostId: 1, 
    postsById: { 
    1: { ... }, 
    2: { ... }, 
    3: { ... } 
    } 
} 

Jetzt können Sie so viele Beiträge zur gleichen Zeit holen, wie Sie mögen, und sie unabhängig voneinander in die postsById Cache verschmelzen ohne Sorgen, ob der abgerufene Post der aktuelle ist.

In Ihrer Komponente würden Sie immer lesen state.postsById[state.currentPostId], oder besser, exportieren Sie getCurrentPost(state) Selektor aus der Reducer-Datei, so dass die Komponente nicht von bestimmten Zustand Form abhängt.

Jetzt gibt es keine Race-Bedingungen und Sie haben einen Cache von Posts, so dass Sie nicht holen müssen, wenn Sie zurückgehen. Wenn Sie später möchten, dass der aktuelle Beitrag über die URL-Leiste gesteuert wird, können Sie currentPostId vollständig aus dem Redux-Status entfernen und stattdessen von Ihrem Router lesen - der Rest der Logik bleibt unverändert.


Während dies nicht genau das gleiche ist, habe ich zufällig ein anderes Beispiel mit einem ähnlichen Problem. Schauen Sie sich die code before und die code after.Es ist nicht ganz das gleiche wie Ihre Frage, aber hoffentlich zeigt es, wie staatliche Organisation Race Conditions und inkonsistente Requisiten zu vermeiden helfen kann.

Ich habe auch eine kostenlose Video-Serie aufgezeichnet, die diese Themen erläutert, so dass Sie vielleicht check it out möchten.

+0

Ich habe ein bisschen andere Situation. Was soll ich tun, wenn ich auch 'currentPostId' und' postById' habe, aber ich muss Daten über alle Beiträge abrufen (Zeitpunkt der Erstellung, Vorschaubild, Meta usw.) und dann den Inhalt von nur einem ausgewählten Beitrag abrufen und Cache diesen Inhalt mit allem anderen über diesen Beitrag in 'postById'? – denysdovhan

+0

@DanAbramov Hallo Ich weiß, dass dies eine ziemlich alte Frage ist, aber da jeder Aktionsersteller keinen Zugriff auf den Status erhält, wie kann der Aktionsersteller entscheiden, ob er zwischengespeichert wurde oder ob er das entfernte Objekt holen muss? – Vinz243

+0

Was ist, wenn Sie 'currentListOfSearchResults' haben, welches ein Array ist, anstatt nur 'currentPost'? Normalerweise überspringe ich Redux für diese Ergebnisse und setze sie nur in den lokalen Zustand für meine Seitenkomponente. –

3

Dans Lösung ist wahrscheinlich eine bessere, aber eine alternative Lösung besteht darin, die erste Anfrage abzubrechen, wenn die zweite beginnt.

Sie können dies tun, indem Sie Ihren Aktionsersteller in eine asynchrone Einheit aufteilen, die aus dem Speicher lesen und andere Aktionen ausführen kann, die redux-thunk Ihnen erlaubt.

Das erste, was Ihr Assistent für asynchrone Aktionen tun sollte, ist, das Geschäft nach einem bestehenden Versprechen zu durchsuchen und es abzubrechen, falls es ein solches gibt. Ist dies nicht der Fall, kann die Anfrage gestellt und eine Aktion "Anfrage beginnt" versandt werden, die das Versprechensobjekt enthält, das für das nächste Mal gespeichert wird.

Auf diese Weise wird nur das zuletzt erstellte Versprechen aufgelöst. Wenn dies der Fall ist, können Sie eine Erfolgsaktion mit den empfangenen Daten versenden. Sie können auch eine Fehleraktion versenden, wenn das Versprechen aus irgendeinem Grund abgelehnt wird.

+5

http://i.kinja-img.com/gawker-media/image/upload/aoz8kgx8pzknypz7z38n.jpg –

+5

Beachten Sie, dass wir nicht empfehlen, Versprechen in diesem Zustand zu speichern. Idealerweise sollte es serialisierbar sein. Ich beschreibe jedoch den Ansatz, den Sie in [dieser Lektion] vorgeschlagen haben (https://egghead.io/lessons/javascript-redux-avoiding-race-conditions-with-thunks?course=building-react-applications-with- idiomatische Redukte). Es ist ein guter Ansatz, ersetzt aber nicht den Normalisierungszustand. (Die Kombination beider Ansätze ist eine gute Idee.) –

Verwandte Themen