Queuing Aktionen in Redux
Habe ich momentan noch eine situation, ich brauche wiedermal Aktionen, die ausgeführt werden, nacheinander. Ich habe schon einen Blick auf die verschiedenen middlewares, wie eine redux-Versprechen, die scheinen in Ordnung zu sein wenn Sie wissen, was das aufeinander folgende Aktionen sind an der Stelle der Wurzel (aus Mangel an eines besseren Begriffs) Aktion ausgelöst wird.
Im wesentlichen möchte ich pflegen eine queue von Aktionen, die Hinzugefügt werden können, um an jedem beliebigen Punkt. Jedes Objekt hat eine Instanz dieser Warteschlange in seinem Zustand abhängig und können die Aktionen in die Warteschlange eingereiht, verarbeitet und aus der Warteschlange entfernt entsprechend. Ich habe eine Implementierung, aber damit ich Zugriff auf bin Stand in meinem handeln Machern, die sich anfühlt wie ein anti-pattern.
Werde ich versuchen, und geben Sie den Zusammenhang der auf-und Umsetzung.
Use-Case -
Angenommen, Sie erstellen möchten einige Listen und speichern Sie Sie auf einem server. Bei der Listenerstellung, der server antwortet mit einer id für die Liste, die in nachfolgenden API-Endpunkte in Bezug auf die Liste:
http://my.api.com/v1.0/lists/ //POST returns some id
http://my.api.com/v1.0/lists/<id>/items //API end points include id
Vorstellen, dass der client ausführen möchte, optimistisch-updates auf diese API-Punkte zu verbessern, UX - niemand wird gerne auf der Suche auf Spinner. Also, wenn Sie erstellen eine Liste, die neue Liste sofort angezeigt wird, mit einer option auf Elemente hinzufügen:
+-------------+----------+
| List Name | Actions |
+-------------+----------+
| My New List | Add Item |
+-------------+----------+
Nehme an, dass jemand versucht, ein Element hinzuzufügen, bevor die Antwort von der ersten create-Aufruf gemacht hat, es zurück. Der Artikel API ist abhängig von der id, so dass wir wissen können wir es nicht nennen, bis wir haben, die Daten. Aber vielleicht möchten wir optimistisch zeigen die neue Position und enqueue-Aufruf der API-Elemente, so dass es ausgelöst wird, sobald der create-Aufruf erfolgt ist.
Eine Mögliche Lösung
Die Methode, die ich verwende, um diese aktuell ist, indem jeder Liste eine action-queue - das ist eine Liste von Redux Aktionen, die ausgelöst wird, in Folge.
Reducer-Funktionen für die Listen-Erstellung könnte wie folgt Aussehen:
case ADD_LIST:
return {
id: undefined, //To be filled on server response
name: action.payload.name,
actionQueue: []
}
Dann, in einer Aktion Schöpfer, wir würden enqueue-eine Aktion statt, die direkt auslösende es:
export const createListItem = (name) => {
return (dispatch) => {
dispatch(addList(name)); //Optimistic action
dispatch(enqueueListAction(name, backendCreateListAction(name));
}
}
Kürze halber wird angenommen, dass die backendCreateListAction Funktion ruft eine fetch API, die Nachrichten verteilt, um dequeue aus der Liste auf Erfolg/Misserfolg.
Das Problem
Was mich beunruhigt, ist hier die Umsetzung der enqueueListAction Methode. Dies ist, wo ich bin Zugriff auf Staat zu regieren, die Weiterentwicklung der Warteschlange. Es sieht wie folgt aus (ignorieren Sie das passende auf name - dieser name tatsächlich verwendet ein clientId in der Realität, aber ich versuche, damit das Beispiel einfach):
const enqueueListAction = (name, asyncAction) => {
return (dispatch, getState) => {
const state = getState();
dispatch(enqueue(name, asyncAction));{
const thisList = state.lists.find((l) => {
return l.name == name;
});
//If there's nothing in the queue then process immediately
if (thisList.actionQueue.length === 0) {
asyncAction(dispatch);
}
}
}
Hier davon ausgehen, dass der enqueue-Methode gibt einen einfachen Aktion fügt eine asynchrone Aktion in die Listen actionQueue.
Die ganze Sache fühlt sich ein bisschen gegen den Strich, aber ich bin mir nicht sicher ob es einen anderen Weg mit ihm zu gehen. Zusätzlich, da ich zum Versand in meinen asyncActions, ich übergeben zu müssen, den Versandweg zu Ihnen hinunter.
Gibt es ähnlichen code in der Methode dequeue aus der Liste, die löst die nächste Aktion sollte ein vorhanden:
const dequeueListAction = (name) => {
return (dispatch, getState) => {
dispatch(dequeue(name));
const state = getState();
const thisList = state.lists.find((l) => {
return l.name === name;
});
//Process next action if exists.
if (thisList.actionQueue.length > 0) {
thisList.actionQueue[0].asyncAction(dispatch);
}
}
Generell kann ich mit Leben, aber ich bin besorgt, dass es ein anti-pattern sein, und es könnte sein, ein präziser, idiomatische Weise, dies zu tun, in Redux.
Jede Hilfe ist willkommen.
- Nehmen Sie einen Blick an redux-saga.
- Dies ist eine große Frage, werde ich daran interessiert zu sehen, ob jemand eine elegante Lösung dafür. Ich habe mit Versprechen Ketten + mapDispatchToProps zu erreichen etwas ähnliches.
- Ich denke, dass wir alle das getan haben, tief Tauchen Sie ein in reagieren/redux gefunden haben, müssen queue/Aggregat Aktionen in einer Weise, die nicht unterstützt wird, die speziell in einem der middleware-Optionen. Ich persönlich denke, du bist besser dran, Umgang mit es auf der Komponenten-Ebene, und tracking/Vergleich-Stand es, statt in der middleware, wo Sie müsste queue/dequeue-IDs.
- Korrigieren Sie mich, wenn ich falsch Liege, aber ich glaube nicht, dass redux-saga, löst dieses Problem. Von dem, was ich sammeln können, von der Dokumentation, non-concurrent requests für die gleiche Aktion, die abgeholt werden, die von der saga stornieren ausstehende Anforderungen. Zugegeben, ich habe noch nicht gegraben zu tief in das Zeug noch.
- Ich hoffe auf eine elegantere Lösung zu. Ich fühle mich immer noch wie ich vielleicht etwas grundlegend falsch oder nicht-idiomatische hier. Ich wäre interessiert zu sehen, Ihre Versprechen Ketten-Ansatz - funktioniert das, wenn Sie nicht wissen, die volle Versprechen Kette, wenn die Auslösung der ersten Aktion? Das war die größte Hürde für mich - ich muss in der Lage sein, beliebig hinzufügen und entfernen, die von dieser Warteschlange, wo Handlungen nicht wissen, ob es aufeinander folgende Aktionen.
- Das ist interessant. Ich habe zunächst versucht zu lösen, in den Komponenten, aber ich war die überprüfung Zustand und Trigger-Aktionen in Warteschlangen, wenn Sie es in der render-Methode (also, dass, Wann immer gab es ein update, das Warteschlange könnte geprüft werden, und tickte vorbei). Dies trifft nicht Fliegen - Redux produziert eine Fehlermeldung über die render-Methode restlichen rein.
- Ich würde den "Prozess" lokale Warteschlange" der Logik in eines der lifecycle-Methoden wie willReceiveProps.
- Der Zugriff auf state-innen Ihre Aktion Schöpfer ist nicht ein anti-pattern. Mutiert wäre. Sehen Sie diese Umsetzung. github.com/cerebral/redux-action-tree
- Ein großes Problem würde ich vorsichtig sein, der versucht, auf Daten zuzugreifen, die noch nicht existiert, und ich würde auf jeden Fall nicht zulassen, dass Zugriff auf Zustand von asynchronen Aktionen.
- Also lassen Sie mich sicherstellen, dass ich verstehe. Sie möchten eine Warteschlange erstellen, der alle Aktionen, die ein Benutzer macht, das passiert in der Folge, sobald die Daten von der service zeigt sich? Auch nach oben, dass off Sie wollen, dass es auch mit dem async-Aktionen?
- Check-out redux-Logik codewinds.com/blog/2016-08-16-business-logic-redux.html
Du musst angemeldet sein, um einen Kommentar abzugeben.
Habe ich das perfekte Werkzeug für das, was Sie suchen. Wenn Sie eine Menge Kontrolle über redux, (vor allem etwas asynchron) und Sie brauchen wiedermal Handlungen passieren nacheinander gibt es kein besseres Werkzeug als Redux Sagen. Es ist erbaut auf dem Gipfel des es6-Generatoren geben Ihnen eine Menge Kontrolle, da Sie können, in einem Sinn, Ihren code anhalten an bestimmten Punkten.
Den action queue Sie beschreiben ist, was man eine saga. Jetzt, da es ist geschaffen, um die Arbeit mit redux diese sagas ausgelöst werden können, zum ausführen von durch die Entsendung in Ihre Komponenten.
Seit Sagas verwenden Generatoren können Sie auch sicher mit der Gewissheit, dass Ihre Einsätze in einer bestimmten Reihenfolge auftreten und passiert nur unter bestimmten Bedingungen. Hier ist ein Beispiel aus Ihrer Dokumentation, und ich gehen Sie durch, es mal zu veranschaulichen was ich meine:
Okay, es sieht ein wenig verwirrend auf den ersten, aber diese saga legt die genaue Reihenfolge eine login-Sequenz geschehen muss. Die Endlosschleife ist zulässig, da die Art der Generatoren. Wenn Ihr code wird Ertrag es halt an, dass die Reihe stellen und warten. Es wird nicht weiter auf die nächste Zeile, bis Sie sagen, es zu. Also schauen, wo es heißt
yield take('LOGIN_REQUEST')
. Die saga wird der Ertrag oder warten Sie an diesem Punkt, bis Sie Versand 'LOGIN_REQUEST', nach dem die saga nennen die autorisieren Methode, und gehen bis zum nächsten yield. Die nächste Methode ist eine asynchroneyield call(Api.storeItem, {token})
so wird es nicht gehen, um die nächste Zeile, bis dieser code behebt.Nun, das ist, wo die Magie passiert. Die saga wird halt wieder auf
yield take('LOGOUT')
bis Sie Versand LOGOUT in Ihrer Anwendung. Dies ist entscheidend, denn wenn Sie die waren zum Versand LOGIN_REQUEST wieder vor dem LOGOUT der login-Prozess würde nicht aufgerufen werden. Nun, wenn Sie Versand ABMELDUNG wird die Schleife zurück zum ersten Ertrag und warten Sie, bis die Anwendung zum Versand LOGIN_REQUEST wieder.Redux Sagas sind, mit Abstand, eines meiner Lieblings-tools zu verwenden, mit Redux. Es gibt Ihnen so viel Kontrolle über Ihre Anwendung und wer liest dein code wird es Ihnen danken, da jetzt alles liest eine Zeile zu einem Zeitpunkt.
Haben Sie einen Blick auf diese: https://github.com/gaearon/redux-thunk
Die id alleine nicht durch den Druckminderer. In Ihrem action-creator (thunk), abrufen der Liste id der ersten, und dann() führen Sie einen zweiten Anruf hinzufügen, um das Element der Liste. Nach dieser, Sie können Versand verschiedene Aktionen basierend auf, ob oder nicht die addition erfolgreich war.
Können Sie mehrfache Aktionen dabei, um zu berichten, wenn die server-Interaktion hat begonnen und beendet werden. Dies ermöglicht es Ihnen zu zeigen, eine Nachricht oder ein spinner, im Fall der operation ist schwer, und es könnte eine Weile dauern.
Eine eingehendere Analyse finden Sie hier: http://redux.js.org/docs/advanced/AsyncActions.html
Alle Kredit-Dan Abramov
Brauchen Sie nicht zu tun haben mit queuing-Aktionen. Wird es zu verbergen die Daten fließen und es wird Ihre app mehr mühsam zu Debuggen.
Ich schlage vor, Sie verwenden einige temporäre ids beim erstellen einer Liste oder ein Element, und aktualisieren Sie dann diese ids, wenn Sie diese tatsächlich erhalten die echten aus dem store.
Etwas wie das vielleicht ? (nicht getestet, aber Sie bekommen die id) :
BEARBEITEN : ich habe zuerst gar nicht verstehen, dass die Elemente werden automatisch gespeichert, wenn die Liste gespeichert wird. Ich bearbeitet die
createList
Aktion creator.FakeItemApi
in Ihrem Fall erforderlich, die server-ID ausFakeListApi
als Teil der url-Zeichenfolge, wie im obigen Beispiel. Jetzt Stell dir vor, wenn jemand versuchte sofort ein Listenelement hinzufügen, bevor wir garantiert haben, die list-id. Was würde Sie vergehen, wie das zweite argumentcreateList
? Eine temp-ID wäre sicherlich brechen die API-Aufruf.FakeItemApi
Anrufe in dercreateList
action Schöpfer. Siehe meine aktualisierte Antwort oben.Dies ist, wie ich dieses problem angehen:
Stellen Sie sicher, dass jede lokale Liste haben eine eindeutige Kennung. Ich spreche nicht über die backend-id hier. Der Name ist wahrscheinlich nicht genug, um eine Liste? Ein "optimistisches" Liste, die noch nicht persistiert, sollte eindeutig identifizierbar sein, und der Benutzer kann versuchen, zu erstellen 2 Listen mit dem gleichen Namen, auch wenn es ein Grenzfall.
Auf Listen-Erstellung, hinzufügen von ein Versprechen von backend-id, um einen cache -
Auf Element hinzufügen, versuchen Sie, die backend-id von Redux-Shop. Wenn es nicht vorhanden ist, dann versuchen, es aus
CreatedListIdCache
. Die id zurückgegeben werden müssen, async, weil CreatedListIdCache gibt ein Versprechen.Verwenden Sie diese Methode in Ihrem
addItem
, so dass Ihre addItem verzögert sich automatisch, bis die backend-id verfügbar istMöchten Sie vielleicht zu reinigen Sie die CreatedListIdPromiseCache, aber es ist wahrscheinlich nicht sehr wichtig für die meisten apps, es sei denn, Sie haben sehr strenge Speicheranforderungen der Verwendung.
Andere Möglichkeit wäre, dass die backend-id berechnet sich auf frontend, mit so etwas wie UUID. Ihr backend müssen nur überprüfen Sie die Eindeutigkeit dieser id. Damit würden Sie immer haben eine gültige backend-id für alle optimistisch erstellt Listen, auch wenn backend nicht Antworten noch.
Ich Stand vor einem ähnlichen problem zu verkaufen. Ich brauchte eine Warteschlange, um zu garantieren, dass optimistische Handlungen begangen wurden oder schließlich verpflichtet (im Falle von Problemen) zu remote-server in der gleichen Reihenfolge Sie erstellt wurden, oder rollback, wenn nicht möglich. Ich fand, dass mit Redux nur, fällt kurz für diese, im Grunde, weil ich glaube, es war dafür nicht ausgelegt und Sie tun es mit Versprechungen allein kann wirklich ein schwieriges problem zu Grund, neben der Tatsache, dass Sie brauchen, um zu verwalten Sie Ihre Warteschlange Zustand irgendwie... IMHO.
Ich denke, @Pcriulan Vorschlag über die Verwendung redux-saga war ein guter. Auf den ersten Blick, redux-saga nicht etwas zu helfen, Sie mit, bis Sie Kanäle. Dies öffnet Ihnen eine Tür zum Umgang mit Parallelität in andere Wege, andere Sprachen tun, CSP-spezifisch (siehe Go oder Clojure ist async zum Beispiel), Dank JS-Generatoren. Es gibt sogar Fragen warum ist benannt nach der Saga Muster und nicht CSP haha... sowieso.
Also hier ist, wie eine sage, könnte Ihnen helfen, mit Ihrer Warteschlange:
Also hier der interessante Teil ist, wie der Kanal wirkt wie ein Puffer (eine Warteschlange), das hält das "Zuhören" für eingehende Aktionen aber nicht gehen Sie mit zukünftigen Aktionen, bis Sie fertig sind mit der aktuellen. Möglicherweise müssen Sie gehen über Ihre Dokumentation, um zu begreifen, den code besser, aber ich denke, es lohnt sich. Die zurücksetzen-Kanal-Teil könnte oder nicht für Ihre Bedürfnisse :denken:
Hoffe, es hilft!