Redux: Ein Problem bei combinReducer, wenn eine Aktion mit mehreren Reduzierern zusammenhängt

Erstellt am 21. Aug. 2015  ·  52Kommentare  ·  Quelle: reduxjs/redux

Ich arbeite an einer Chat-App, die mit React.js und Flux erstellt wurde. Während ich die Dokumente von Redux lese, erscheint mir die Funktion combineReducer seltsam. Zum Beispiel:

Es gibt drei Geschäfte mit den Namen messageStore , unreadStore , threadStore . Und es gibt eine Aktion namens NEW_MESSAGE . Alle drei Geschäfte werden bei neuen Nachrichten aktualisiert. In Redux sind die Geschäfte Reduzierer, kombiniert mit combineReducer wie:

message = (state=[], action) ->
  switch action.type
    when NEW_MESSAGE
      state # new state
unread = (state=[], action) ->
  switch action.type
    when NEW_MESSAGE
      state # new state
thread = (state=[], action) ->
  switch action.type
    when NEW_MESSAGE
      state # new state

combineReducer {message, unread, thread}

Es scheint mir, dass in solchen Fällen das Aufteilen von Reduzierern mein Leben nicht einfacher macht. Allerdings ist ein riesiger Laden, der mit unveränderlichen js gebaut wurde, vielleicht eine bessere Lösung dafür. Mögen:

initialState = Immutable.fromJS
  message: []
  unread: []
  thread: []

allStore = (store=initialState, action) ->
  switch action.type
    when NEW_MESSAGE
        initialState
        .updateIn ['message'], (messages) -> messages # updated
        .updateIn ['urnead'], (urneads) -> unreads # updated
        .updateIn ['thread'], (threads) -> threads # updated
question

Hilfreichster Kommentar

Für eine solche Aktion muss ich sie in mehreren Reduzierungen (oder Dateien, nachdem meine App gewachsen ist) verwalten. In unserer aktuellen Codebasis haben wir bereits 5 Geschäfte gesehen, die dieselbe Aktion ausführen. Die Wartung ist etwas schwierig.

Ob dies gut oder schlecht ist, hängt von Ihrer App ab. Nach meiner Erfahrung ist es sehr praktisch, wenn viele Geschäfte (oder Redux-Reduzierer) dieselbe Aktion ausführen, da dadurch die Verantwortlichkeiten aufgeteilt werden und die Mitarbeiter an Feature-Filialen arbeiten können, ohne mit dem Rest des Teams zusammenzustoßen. Nach meiner Erfahrung ist es einfacher, nicht verwandte Mutationen getrennt zu pflegen als eine Riesenmutation.

Aber Sie haben Recht, es gibt Fälle, in denen dies nicht gut funktioniert. Ich würde sagen, sie sind oft Symptome eines suboptimalen Zustandsmodells. Zum Beispiel,

In einigen Fällen kann ein Reduzierer von Daten eines anderen abhängen (z. B. das Verschieben einer Nachricht von ungelesen in eine Nachricht oder sogar das Heraufstufen einer Nachricht als neuer Thread von Nachricht zu Thread).

ist ein Symptom eines Problems. Wenn Sie viel in Ihrem Zustand bewegen müssen, muss die Zustandsform möglicherweise stärker normalisiert werden. Anstelle von { readMessages, unreadMessages } Sie { messagesById, threadsById, messageIdsByThreadIds } . Nachricht gelesen? Gut, schalten Sie state.messagesById[id].isRead . Möchten Sie Nachrichten für einen Thread lesen? Schnapp dir state.threadsById[threadId].messageIds und dann messageIds.map(id => state.messagesById[id]) .

Wenn Daten normalisiert werden, müssen Sie keine Elemente von einem Array in ein anderes Array kopieren. In den meisten Fällen würden Sie Flags umdrehen oder IDs hinzufügen / entfernen / zuordnen / trennen.

Alle 52 Kommentare

Können Sie erläutern, warum Sie denken, dass combineReducers in diesem Fall nicht hilfreich ist? In der Tat möchten Sie für einen datenbankähnlichen Status häufig einen einzelnen Reduzierer (und dann andere Reduzierer für den UI-Status, z. B. ein ausgewähltes Element usw.).

Siehe auch:

https://github.com/rackt/redux/tree/master/examples/real-world/reducers
https://github.com/rackt/redux/blob/master/examples/async/reducers

für Inspiration.

Das erste Beispiel, auf das Sie hingewiesen haben, ähnelt meinem Fall: requestType wird in beiden Reduzierern behandelt.
https://github.com/rackt/redux/blob/master/examples/real-world/reducers/paginate.js#L28

Das Aufteilen von Reduzierstücken erleichtert die Wartung, wenn zwei Reduzierstücke von anderen getrennt sind. Wenn sie jedoch dieselben Aktionen ausführen, führt dies zu:

  • Für eine solche Aktion muss ich sie in mehreren Reduzierungen (oder Dateien, nachdem meine App gewachsen ist) verwalten. In unserer aktuellen Codebasis haben wir bereits 5 Geschäfte gesehen, die dieselbe Aktion ausführen. Die Wartung ist etwas schwierig.
  • In einigen Fällen kann ein Reduzierer von Daten eines anderen abhängen (z. B. das Verschieben einer Nachricht von unread nach message oder sogar das Heraufstufen einer Nachricht als neuer Thread von message nach thread ). Daher muss ich jetzt möglicherweise Daten von einem anderen Reduzierer abrufen.

Ich sehe keine gute Lösung für diese beiden Probleme. Eigentlich bin ich mir bei diesen beiden Problemen nicht ganz sicher, sie sind wie Kompromisse.

Für eine solche Aktion muss ich sie in mehreren Reduzierungen (oder Dateien, nachdem meine App gewachsen ist) verwalten. In unserer aktuellen Codebasis haben wir bereits 5 Geschäfte gesehen, die dieselbe Aktion ausführen. Die Wartung ist etwas schwierig.

Ob dies gut oder schlecht ist, hängt von Ihrer App ab. Nach meiner Erfahrung ist es sehr praktisch, wenn viele Geschäfte (oder Redux-Reduzierer) dieselbe Aktion ausführen, da dadurch die Verantwortlichkeiten aufgeteilt werden und die Mitarbeiter an Feature-Filialen arbeiten können, ohne mit dem Rest des Teams zusammenzustoßen. Nach meiner Erfahrung ist es einfacher, nicht verwandte Mutationen getrennt zu pflegen als eine Riesenmutation.

Aber Sie haben Recht, es gibt Fälle, in denen dies nicht gut funktioniert. Ich würde sagen, sie sind oft Symptome eines suboptimalen Zustandsmodells. Zum Beispiel,

In einigen Fällen kann ein Reduzierer von Daten eines anderen abhängen (z. B. das Verschieben einer Nachricht von ungelesen in eine Nachricht oder sogar das Heraufstufen einer Nachricht als neuer Thread von Nachricht zu Thread).

ist ein Symptom eines Problems. Wenn Sie viel in Ihrem Zustand bewegen müssen, muss die Zustandsform möglicherweise stärker normalisiert werden. Anstelle von { readMessages, unreadMessages } Sie { messagesById, threadsById, messageIdsByThreadIds } . Nachricht gelesen? Gut, schalten Sie state.messagesById[id].isRead . Möchten Sie Nachrichten für einen Thread lesen? Schnapp dir state.threadsById[threadId].messageIds und dann messageIds.map(id => state.messagesById[id]) .

Wenn Daten normalisiert werden, müssen Sie keine Elemente von einem Array in ein anderes Array kopieren. In den meisten Fällen würden Sie Flags umdrehen oder IDs hinzufügen / entfernen / zuordnen / trennen.

Ich möchte das versuchen.

Ich habe gerade eine Demoseite geschrieben, um Redux auszuprobieren. Ich denke, combineReducer hat mir einige Komplexität gebracht. Und alle Beispiele im Repo verwenden combineReducer . Gibt es immer noch die Möglichkeit, dass ich ein einzelnes unveränderliches Datenobjekt als Modell anstelle eines JavaScript-Objekts verwenden kann, das meine Daten enthält?

@jiyinyiyong Ich bin mir wirklich nicht sicher, über welche Komplexität Sie sprechen, aber Sie können combineReducers . Es ist nur ein Helfer. Schauen Sie sich einen ähnlichen Helfer an, der stattdessen Immutable verwendet . Oder schreiben Sie natürlich Ihre eigenen.

Ich habe meinen eigenen combineReducers() -Helfer geschrieben, hauptsächlich um Immutable.js zu unterstützen, aber es können zusätzliche Reduzierer erforderlich sein, die wie Root-Reduzierer behandelt werden:
https://github.com/jokeyrhyme/wow-healer-bootcamp/blob/master/utils/combineReducers.js

Das erste Argument entspricht der Form Ihres Zustands und übergibt die Unterzustände der obersten Ebene an die von Ihnen bereitgestellten übereinstimmenden Unterreduzierer. Dies entspricht mehr oder weniger der offiziellen Version.

Es ist jedoch optional, null oder mehr zusätzliche Argumente hinzuzufügen. Diese werden wie andere Root-Reduzierer behandelt. Diese Reduzierstücke werden im vollständigen Zustand übergeben. Auf diese Weise können Aktionen auf eine Weise ausgelöst werden, die mehr Zugriff erfordert als das empfohlene Subreduzierungsmuster.

Es ist offensichtlich keine gute Idee, dies zu missbrauchen, aber ich habe den seltsamen Fall gefunden, in dem es schön ist, mehrere Subreduzierer sowie mehrere Wurzelreduzierer zu haben.

Anstatt mehrere Root-Reduzierer hinzuzufügen, könnte ein mittlerer Schritt darin bestehen, die zusätzlichen Argumente zu verwenden, um Subreduzierer zu definieren, die einen breiteren Zugriff benötigen (immer noch weniger als der Gesamtzugriff). Zum Beispiel:

function a (state, action) { /* only sees within a */ return state; }
function b (state, action) { /* only sees within b */ return state; }
function c (state, action) { /* only sees within c */ return state; }

function ab (state, action) { /* partial state containing a and b */ return state; }
function bc (state, action) { /* partial state containing b and c */ return state; }

let rootReducer = combineReducers(
  { a, b, c },
  [ 'a', 'b', ab ],
  [ 'b', 'c', bc ]
);

Lohnt es sich, eine PR für Teilreduzierer und zusätzliche Wurzelreduzierer einzureichen? Ist das eine schreckliche Idee oder könnte dies für genug andere Menschen nützlich sein?

Ich möchte eher lernen, wie Halogen und Ulme mit diesen Problemen umgehen. JavaScript besteht aus mehreren Programmdiagrammen und führt uns zu vielen Möglichkeiten. Ich bin jetzt verwirrt, welches der richtige für FRP und unidirektionalen Datenfluss ist.

Ich habe festgestellt, dass dies auch für mich ein Punkt der Verwirrung ist. Posten Sie einfach meinen Anwendungsfall / meine Lösung hier.

Ich habe drei Subreduzierer, die ein Array von drei verschiedenen Ressourcentypen (Clients, Projekte, Jobs) verarbeiten. Wenn ich einen Client lösche ( REMOVE_CLIENT ), müssen alle zugehörigen Projekte und Jobs gelöscht werden. Jobs sind über ein Projekt mit Clients verbunden, daher muss der Jobreduzierer Zugriff auf die Projekte haben, um die Verknüpfungslogik ausführen zu können. Der Jobreduzierer muss eine Liste aller Projekt-IDs abrufen, die zu dem zu löschenden Client gehören, und dann alle übereinstimmenden Jobs aus dem Geschäft entfernen.

Ich habe dieses Problem mit der folgenden Middleware umgangen:

export default store => next => action => {
  next({ ...action, getState: store.getState });
}

Damit jede Aktion über action.getState() Zugriff auf den Root-Store hat.

@ rhys-vdw danke dafür! Eine wirklich hilfreiche Middleware :)

@ rhys-vdw Danke dafür - scheint eine schöne Lösung zu sein. Wie gehen Sie mit Randfällen / referenzieller Integrität um, z. B. wenn eine Entität von zwei (oder mehr) anderen Entitäten gemeinsam genutzt wird oder wenn Sie beim Löschen auf einen nicht vorhandenen Datensatz verweisen. Ich bin nur gespannt auf deine Gedanken dazu.
@gaearon Gibt es in Redux einen dokumentierten Weg, wie diese Art von referenziellem Integritätsproblem normalisierter Entitäten gelöst werden kann?

Es gibt keinen bestimmten Weg. Ich würde mir vorstellen, dass man dies automatisieren kann, indem man ein Schema definiert und Reduzierungen basierend auf diesem Schema generiert. Sie würden dann „wissen“, wie man „Müll“ sammelt, wenn Dinge gelöscht werden. Dies ist jedoch alles sehr wellig und erfordert Implementierungsaufwand :-). Persönlich glaube ich nicht, dass das Löschen oft genug vorkommt, um eine echte Bereinigung zu rechtfertigen. Normalerweise würde ich ein status -Feld im Modell umdrehen und sicherstellen, dass bei der Auswahl keine gelöschten Elemente angezeigt werden. Natürlich können Sie beim Löschen auch aktualisieren, wenn dies keine übliche Operation ist.

@gaearon Prost auf die superschnelle Antwort. Ja… ich denke, man hat die gleichen Anstrengungen im Umgang mit referenzieller Integrität, wenn man Referenzen mit dokumentbasierten DBs wie Mongo verwendet. Ich denke, es lohnt sich entweder , einen reinen Zustand zu haben, in dem keine Müllwesen herumschweben. Ich bin mir nicht sicher, ob diese Geistereinträge Sie jemals stören könnten, besonders wenn Sie viele CRUD-Operationen in Ihrem Bundesstaat haben.

Ich denke jedoch auch, dass es ein Ass wäre, wenn Redux sowohl normalisierte als auch eingebettete Entitäten unterstützen würde. Und man könnte eine Strategie wählen, die zum Zweck passt. Aber ich denke, für eingebettete Entitäten muss man nested reducers was ein Anti-Pattern gemäß den Redux-Dokumenten ist.

Ich fand es schwierig, in einem Reduzierer auf den Rest des Geschäftszustands zuzugreifen. In einigen Fällen ist es daher sehr schwierig, verschachtelte Mähdrescher zu verwenden. Es wäre gut, wenn es Methoden gäbe, um auf den Rest des Staates zuzugreifen, wie .parent() oder .root() oder gleichwertig.

Der Zugriff auf den Zustand anderer Reduzierstücke in einem Reduzierstück ist meistens ein Anti-Muster.
Es kann normalerweise gelöst werden durch:

  • Entfernen dieser Logik aus dem Reduzierstück und Verschieben in einen Selektor;
  • Übergabe zusätzlicher Informationen an die Aktion;
  • Lassen Sie den Ansichtscode zwei Aktionen ausführen.

Vielen Dank! Ich stimme dem zu. Ich habe gerade festgestellt, dass es komplexer wurde, nachdem ich versucht hatte, den Wurzelzustand in Reduzierer umzuwandeln :-)

Ja, das Problem beim Übergeben des Wurzelzustands besteht darin, dass Reduzierer an die Zustandsform des anderen gekoppelt werden, was jede Umgestaltung oder Änderung der Zustandsstruktur erschwert.

Okay, ich sehe deutlich die Vorteile eines normalisierten Zustands und behandle den Redux-Teil meiner Zustandsart wie eine DB.

Ich denke immer noch darüber nach, ob der Ansatz von @gaearon ( Kennzeichnen von Entitäten zum Löschen) oder @ rhys-vdw besser ist (Auflösen der Referenzen und Entfernen verwandter Referenzen an Ort und Stelle) .

Irgendwelche Ideen / Erfahrungen aus der realen Welt?

Wenn Redux eher wie Ulme war und der Zustand nicht normalisiert wurde, ist es sinnvoll, Reduzierer von der Zustandsform zu entkoppeln. Da der Status jedoch normalisiert ist, scheinen Reduzierer häufig Zugriff auf andere Teile des App-Status zu benötigen.

Reduzierstücke sind bereits transitiv an die Anwendung gekoppelt, da Reduzierstücke von Maßnahmen abhängen. Sie können Reduzierungen nicht trivial in einer anderen Redux-Anwendung wiederverwenden.

Das Problem zu lösen, indem der Statuszugriff in Aktionen verschoben und Middleware verwendet wird, scheint eine viel hässlichere Form der Kopplung zu sein, als Reduzierungen den direkten Zugriff auf den Stammstatus zu ermöglichen. Jetzt sind Aktionen, die dies normalerweise nicht erfordern, auch an die Zustandsform gekoppelt, und es gibt mehr Aktionen als Reduzierungen und mehr Orte zum Ändern.

Das Übergeben des Status in die Ansicht in Aktionen ist möglicherweise besser, um Änderungen der Statusform zu isolieren. Wenn jedoch ein zusätzlicher Status erforderlich ist, wenn Features hinzugefügt werden, müssen ebenso viele Aktionen aktualisiert werden.

IMHO würde das Zulassen des Zugriffs von Reduzierern auf den Stammzustand zu einer geringeren Gesamtkopplung führen als die aktuellen Problemumgehungen.

@kurtharriger : Beachten Sie, dass das häufig empfohlene Muster darin besteht, den Redux-Status nach Domänen zu strukturieren und combineReducers zu verwenden, um jede Domäne separat zu verwalten. Dies ist jedoch nur eine Empfehlung. Es ist durchaus möglich, einen eigenen Reduzierer der obersten Ebene zu schreiben, der die Dinge anders verwaltet, einschließlich der Übergabe des gesamten Status an bestimmte Subreduziererfunktionen. Letztendlich haben Sie wirklich nur eine Reduzierfunktion, und wie Sie sie intern aufteilen , liegt ganz bei Ihnen. combineReducers ist nur ein nützliches Dienstprogramm für einen allgemeinen Anwendungsfall.

Die Redux-FAQ-Antwort zur Aufteilung der Geschäftslogik enthält auch einige gute Diskussionen zu diesem Thema.

Ja, eine andere Option wäre, nur einen einzelnen Reduzierer in einer riesigen Funktion zu verwenden und / oder zu schreiben, dass Sie eine eigene CombineReducer-Funktion haben, anstatt die integrierte zu verwenden.
Eine integrierte kombinierteReducers-Methode, die ein zusätzliches Root-Statusargument hinzufügt, könnte nützlich sein, aber PRs, die versucht haben, diese Funktionalität hinzuzufügen, wurden als Anti-Pattern geschlossen. Ich wollte nur den Punkt argumentieren, dass die IMHO vorgeschlagenen Alternativen zu einer viel stärkeren Gesamtkopplung führen, und vielleicht sollte dies nicht als Anti-Muster angesehen werden. Auch wenn der Root-Status als zusätzliches Argument hinzugefügt wurde, ist es nicht so, dass Sie gezwungen sind, ihn zu verwenden, sodass die Kopplung immer noch ein Opt-In ist.

Wir möchten, dass diese Kopplung explizit ist. Es ist explizit, wenn Sie es manuell tun, und es sind buchstäblich N Codezeilen, wobei N die Anzahl der Reduzierungen ist, die Sie haben. Verwenden Sie einfach keine kombinierenReduzierer und schreiben Sie diesen übergeordneten Reduzierer von Hand.

Ich bin neu im Reduzieren / Reagieren, würde aber demütig fragen: Wenn zusammengesetzte Reduzierer, die auf den Zustand des anderen zugreifen, ein Anti-Muster sind, weil sie die Kopplung zwischen unterschiedlichen Bereichen der Zustandsform hinzufügen, würde ich argumentieren, dass diese Kopplung bereits in mapStateToProps ( ) von connect (), da diese Funktion den Root-Status bereitstellt und Komponenten von überall / überall abrufen, um ihre Requisiten zu erhalten.

Wenn Statusunterbäume gekapselt werden sollen, müssen Komponenten über Zugriffsmethoden, die diese Unterbäume ausblenden, auf den Status zugreifen müssen, damit die Statusform innerhalb dieser Bereiche ohne Refactoring verwaltet werden kann.

Während ich Redux lerne, habe ich Probleme mit dem, was ich mir als langfristiges Problem mit meiner App vorstelle. Dies ist eine enge Kopplung der Statusform über die App hinweg - nicht durch Reduzierungen, sondern durch mapStateToProps - wie eine Kommentarseitenleiste, die das wissen muss Der Name des Auth-Benutzers (jeder wird beispielsweise durch einen Kommentar-Reduzierer gegenüber einem Auth-Reduzierer reduziert.) Ich experimentiere damit, den gesamten Statuszugriff in Accessor-Methoden zu verpacken, die diese Art der Kapselung ausführen, selbst in mapStateToProps, aber ich habe das Gefühl, dass ich möglicherweise falsch belle Baum.

@jwhiting : Auswahlfunktionen ", um die benötigten Daten aus dem Status zu extrahieren, insbesondere in mapStateToProps . Sie können den Zustand Ihrer Form ausblenden, sodass nur eine minimale Anzahl von Stellen bekannt sein muss, wo sich state.some.nested.field befindet. Wenn Sie es noch nicht gesehen haben, lesen Sie die Computing Derived Data in den Redux-Dokumenten.

Ja. Sie müssen Reselect auch nicht verwenden, wenn Ihr Datenvolumen klein ist, aber dennoch (handgeschriebene) Selektoren verwenden. Schauen Sie sich das Beispiel shopping-cart für diesen Ansatz an.

@markerikson @gaearon das macht Sinn und ich werde das untersuchen, danke. Die Verwendung von Selektoren in mapStateToProps für alles außerhalb des Hauptanliegens dieser Komponente (oder möglicherweise für den gesamten Statuszugriff) würde Komponenten vor Änderungen der Form eines fremden Status schützen. Ich möchte jedoch einen Weg finden, die staatliche Kapselung in meinem Projekt strenger durchzusetzen, damit die Codedisziplin nicht der einzige Schutz dafür ist, und begrüße Vorschläge dort.

Den gesamten Code in einem einzigen Reduzierer zu halten, ist keine wirklich gute Trennung von
Sorgen. Wenn Ihr Reduzierer von der Wurzel aufgerufen wird, können Sie ihn manuell aufrufen
und übergeben Sie explizit den Stammzustand, wie ich denke, dass Sie vorschlagen, aber wenn Ihr
Die übergeordnete Reduzierungshierarchie enthält CombineReducers, die Sie rippen müssen
aus. Warum also nicht kompilierbarer machen, damit Sie es nicht rippen müssen?
später raus oder auf Thunks zurückgreifen, wie es die meisten Leute zu tun scheinen?
Am Samstag, den 16. April 2016 um 20:27 Uhr Josh Whiting [email protected]
schrieb:

@markerikson https://github.com/markerikson @gaearon
https://github.com/gaearon das macht Sinn und ich werde das untersuchen,
Dankeschön. Verwenden von Selektoren in mapStateToProps für alles außerhalb davon
Das Hauptanliegen der Komponente (oder vielleicht für den gesamten staatlichen Zugang) würde abschirmen
Komponenten aus Änderungen der Fremdzustandsform. Ich würde gerne einen Weg finden
um die staatliche Kapselung in meinem Projekt strenger durchzusetzen,
Damit ist die Code-Disziplin nicht die einzige Wache dafür und willkommen
Vorschläge dort.

- -
Sie erhalten dies, weil Sie erwähnt wurden.
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an
https://github.com/reactjs/redux/issues/601#issuecomment -210940305

@kurtharriger : Wir sind uns alle einig, dass es eine schlechte Idee ist, das letzte Stück Reduzierungslogik in einer einzigen Funktion zu halten, und dass die Arbeit auf irgendeine Weise an Unterfunktionen delegiert werden sollte. Jeder wird jedoch unterschiedliche Ideen dazu und unterschiedliche Anforderungen an seine eigene Anwendung haben. Redux bietet einfach combineReducers als integriertes Dienstprogramm für einen allgemeinen Anwendungsfall. Wenn Sie dieses Dienstprogramm nicht verwenden möchten, wie es derzeit vorhanden ist, müssen Sie es nicht - Sie können Ihre Reduzierungen ganz nach Ihren Wünschen strukturieren, egal ob Sie alles von Hand schreiben oder Ihre eigene Variation von combineReducers schreiben

Da wir über Muster und die Trennung von Bedenken sprechen, stoße ich tatsächlich auf etwas Ähnliches, aber stattdessen auf Aktionen.

Das Ajax shouldFetch-Muster, das ich an verschiedenen Stellen sehe, scheint nämlich eine enge Kopplung an die Zustandsform zu sein. Zum Beispiel hier . Was ist in diesem Beispiel, wenn ich den Reduzierer wieder verwende, aber das Objekt "Entities" nicht an der Wurzel des Status habe? Diese Aktion würde dann fehlschlagen. Was ist die Empfehlung hier? Sollte ich fallspezifische Aktionen hinzufügen? Oder eine Aktion, die einen Selektor akzeptiert?

@shadowii Wenn ein Action Creator einen Selektor importiert, scheint mir das in Ordnung zu sein. Ich würde Selektoren mit Reduzierern zusammenfassen, damit das Wissen über die Statusform in diesen Dateien lokalisiert ist.

Was ist das Problem, wenn verschiedene Reduzierstücke denselben Aktionstyp handhaben?
Wenn ich beispielsweise einen account und einen auth Reduzierer habe, behandeln beide den Aktionstyp LOGIN_SUCCESS (mit dem Authentifizierungstoken und den Kontodaten in der Nutzlast) Eine aktualisiert nur den Statusabschnitt, den sie verwalten. Ich habe diesen Ansatz einige Male verwendet, wenn eine Aktion verschiedene Teile des Staates betrifft.

Was ist das Problem, wenn verschiedene Reduzierstücke denselben Aktionstyp handhaben?

Es gibt kein Problem, es ist ein sehr verbreitetes und nützliches Muster. Tatsächlich gibt es die _reason_ Aktionen: Wenn Aktionen 1: 1 auf Reduzierungen abgebildet werden, wäre es wenig sinnvoll, überhaupt Aktionen zu haben.

@ rhys-vdw Ich habe versucht, die von Ihnen gepostete Middleware zu verwenden.

customMiddleare.js

const customMiddleware =  store => next => action => {
  next({ ...action, getState: store.getState });
};

und in meiner Ladenerstellung habe ich

import customMiddleware from './customMiddleware';

var store = createStore(
        rootReducer,
        initialState,
        applyMiddleware(
            reduxPromise,
            thunkMiddleware,
            loggerMiddleware,
            customMiddleware
        )
    );
return store;

Ich erhalte den folgenden Fehler:

applyMiddleware.js? ee15: 49 Ungefangener TypeError: Middleware ist keine Funktion (…)
(anonyme Funktion) @ applyMiddleware.js? ee15: 49
(anonyme Funktion) @ applyMiddleware.js? ee15: 48
createStore @ createStore.js? fe4c: 65
configureStore @ configureStore.js? ffde: 45
(anonyme Funktion) @ index.jsx? fdd7: 25
(anonyme Funktion) @ bundle.js: 1639
webpack_require @ bundle.js: 556
fn @ bundle.js: 87 (anonyme Funktion) @ bundle.js: 588
webpack_require @ bundle.js: 556
(anonyme Funktion) @ bundle.js: 579
(anonyme Funktion) @ bundle.js: 582

@ mars76 : Wahrscheinlich importierst du etwas nicht richtig. Wenn das wirklich der gesamte Inhalt von customMiddleware.js , exportieren Sie nichts. Im Allgemeinen übergeben Sie vier Middlewares an applyMiddleware , und vermutlich ist eine davon keine gültige Referenz. Treten Sie zurück, entfernen Sie alle, fügen Sie nacheinander neue hinzu, prüfen Sie, welche ungültig ist, und finden Sie heraus, warum. Überprüfen Sie die Import- und Exportanweisungen, überprüfen Sie noch einmal, wie Sie die Dinge initialisieren sollen usw.

Hallo,

Danke @markerikson

Ich habe die Syntax wie folgt konvertiert und es ist jetzt in Ordnung:

customMiddleware.js

export function customMiddleware(store) { return function (next) { return function (action) { next(Object.assign({}, action, { getState: store.getState })); }; }; } console.log(customMiddleware);

Wie auch immer, ich versuche, auf den Store im Action Creator selbst zuzugreifen (nicht im Reducer). Ich muss einen Async-Anruf tätigen und verschiedene Teile des Geschäftszustands übergeben, die von verschiedenen Reduzierern verwaltet werden.

Das mache ich. Ich bin mir nicht sicher, ob dies der richtige Ansatz ist.

Ich versende eine Aktion mit den Daten und innerhalb des Reduzierers habe ich jetzt Zugriff auf den gesamten Status. Ich extrahiere die erforderlichen Teile, erstelle ein Objekt und versende eine weitere Aktion mit diesen Daten, die in meinem Aktionsersteller verfügbar sein wird, wo i Ich mache einen Asyn-Anruf mit diesen Daten.

Meine größte Sorge ist, dass Reduzierer jetzt Methoden zum Erstellen von Aktionen aufrufen.

Schätzen Sie alle Kommentare.

Vielen Dank

@ mars76 : Auch hier sollten Sie niemals versuchen, innerhalb eines

Für den Zugriff auf den Store in Ihren Action Creators können Sie die Redux-Thunk- Middleware verwenden. Vielleicht möchten Sie auch die FAQ-Frage zum Datenaustausch zwischen Reduzierern sowie einen Kern lesen, den ich mit einigen Beispielen für gängige Thunk-Verwendungen geschrieben habe .

Vielen Dank @markerikson.

Ich habe nichts über den getState () in Redux-Thunk bemerkt. Das macht meine Arbeit viel einfacher und lässt meine Reduzierungen rein.

@gaearon, als Sie erwähnt haben, dass ein Aktionsersteller einen Selektor importiert , haben Sie gedacht , dass der gesamte Status zur Übergabe an den Selektor an den Aktionsersteller übergeben wird, oder fehlt mir etwas?

@tacomanator : Zu

Wenn Sie einen Selektor in einem Action Creator verwenden, tun Sie dies meistens innerhalb eines Thunks. Thunks haben Zugriff auf getState , sodass auf den gesamten Statusbaum zugegriffen werden kann:

import {someSelector} from "./selectors";

function someThunk(someParameter) {
    return (dispatch, getState) => {
        const specificData = someSelector(getState(), someParameter);

        dispatch({type : "SOME_ACTION", payload : specificData});    
    }
}

@markerikson danke, das macht Sinn. Zu Ihrer Information, der Grund, den ich frage, besteht hauptsächlich darin, die von Selektoren abgeleiteten Daten für die Validierung der Geschäftslogik zu verwenden, anstatt sie an einen Reduzierer zu senden, aber das gibt mir auch Anlass zum Nachdenken.

Ein schneller Hack, den ich verwendet habe, um Reduzierer zu einem einzigen zu kombinieren, war

const reducers = [ reducerA, reducerB, ..., reducerZ ];

let reducer = ( state = initialState, action ) => {
    return reducers.reduce( ( state, reducerFn ) => (
        reducerFn( state, action )
    ), state );
};

Dabei sind reducerA bis reducerZ die einzelnen Reduzierungsfunktionen.

@brianpkelley : Ja, das ist im Grunde https://github.com/acdlite/reduce-reducers .

@markerikson Ah, schön, das erste Mal mit react / redux / etc und habe diesen Thread gefunden, als

Heh, sicher.

Zu Ihrer Information, vielleicht möchten Sie die FAQ durchlesen: "Muss ich combineReducers ?" und Strukturieren von Reduzierern in den Dokumenten. Außerdem enthält meine Tutorial-Reihe "Practical Redux" einige Beiträge, die einige fortgeschrittene Reduziertechniken demonstrieren (siehe Teil 7 und Teil 8).

Meine zwei Cent bei Verwendung von Redux-Logik sind, die Aktion aus dem Stammzustand in der Funktion transform .

Beispiel:

const formInitLogic = createLogic({
  type: 'FORM_INIT',
  transform({ getState, action }, next) {
    const state = getState();
    next({
      ...action,
      payload: {
        ...action.payload,
        property1: state.branch1.someProperty > 5,
        property2: state.branch1.anotherProperty + state.branch2.yetAnotherProperty,
      },
    });
  },
});

Ich denke, es geht mehr um Codestruktur:
"Greifen Sie nicht direkt auf Aktionen in Unterzuständen zu"
Nachdem ich Redux in vielen Projekten verwendet hatte, stellte ich fest, dass die beste Codestruktur darin besteht, jeden Unterzustand als vollständig getrennte Komponente zu behandeln. Jede dieser Komponenten hat einen eigenen Ordner und eine eigene Logik. Um diese Idee umzusetzen, verwende ich die folgende Dateistruktur:

  • Reduxwurzel

    • reducers.js export kombiniert reduzierer aus allen staaten

    • Middleware.js Export ApplyMiddleware für alle Middleware

    • configureStore.js createStore using (reducers.js, middleware.js)

    • action.js exportiert Aktionen, die Aktionen aus Statusordnern <== THIS auslösen

    • state1

    • initial.js Diese Datei enthält den Anfangszustand (ich benutze unveränderlich, um einen Datensatz zu erstellen)

    • action-types.js Diese Datei enthält die Aktionstypen (ich benutze Datscha als Schlüsselspiegel)

    • action.js Diese Datei enthält die Statusaktionen

    • reducer.js Diese Datei enthält den

    • state2

    • initial.js

    • action-types.js

      ....

Warum ?
1- In jeder Anwendung gibt es zwei Arten von Aktionen:

  • Zustand Aktionen (Redux / state1 / actions.js) diese Aktionen Verantwortung nur ihren Zustand ändern, sollten Sie diese Aktionen immer nicht feuern direkt App - Aktionen verwenden

    • App-Aktionen (redux / action.js) Diese Aktionen sind für die Ausführung einer App-Logik verantwortlich und können Statusaktionen auslösen

2- Zusätzlich können Sie mit dieser Struktur einen neuen Status erstellen, indem Sie einen Ordner kopieren, einfügen und umbenennen;)

Was ist, wenn 2 Reduzierer dieselbe Eigenschaft ändern müssen?

Angenommen, ich habe einen Reduzierer, den ich in verschiedene Dateien aufteilen möchte, und es gibt eine Eigenschaft namens "Fehler", die ich ändere, wenn http-Anforderungen aus dem einen oder anderen Grund fehlschlagen.

Irgendwann ist der Reduzierer zu groß geworden, also habe ich beschlossen, ihn auf mehrere Reduzierer aufzuteilen, aber alle müssen diese "Fehler" -Eigenschaft ändern.

Dann was?

@ Wassap124 Es ist nicht möglich, dass zwei Reduzierungen denselben Zustand verwalten. Meinen Sie, dass zwei Aktionen dieselbe Eigenschaft ändern müssen?

Ohne weitere Details bevorzugen Sie möglicherweise die Verwendung eines Thunks, um eine "Set Error" -Aktion auszulösen.

@ rhys-vdw Ich bin nur ein bisschen festgefahren, um einen ziemlich langen Reduzierer aufzuteilen.
Und widerspricht das in Bezug auf Ihren Vorschlag nicht der Regel, dass keine Nebenwirkungen auftreten?

@ Wassap124 , @ rhys-vdw: Zur Verdeutlichung ist es nicht möglich, dass zwei Reduzierer denselben Status verwalten, wenn Sie nur combineReducers _ verwenden. Sie können jedoch sicherlich eine andere Reduzierungslogik schreiben, die zu Ihrem eigenen Anwendungsfall passt - siehe https://redux.js.org/recipes/structuringreducers/beyondcombinereducers .

Wenn Sie eine schnelle Quellen-Höhlenforschung betreiben ...

Das combineReducers gibt eine Funktionssignatur wie diese zurück

return function combination(state = {}, action) {

Siehe https://github.com/reduxjs/redux/blob/master/src/combineReducers.js#L145

nach dem Erstellen eines Verschlusses, um auf das tatsächliche Reduzierobjekt zu verweisen.

Daher können Sie etwas Einfaches hinzufügen, um Aktionen global zu handhaben, wenn der Status hydratisiert und aus dem Speicher neu geladen werden muss.

const globalReducerHandler = () => {
  return (state = {}, action) => {
    if (action.type === 'DO_GLOBAL_THING') {
      return globallyModifyState(state)
    }
    return reducers(state, action)
  }
}

Dies mag ein Anti-Muster sein oder auch nicht, aber Pragmatismus war schon immer wichtiger.

Anstatt einen Reduzierer von einem anderen Reduzierer aufzurufen (was ein Antimuster ist, da globalReducerHandler nichts mit dem von ihm aufgerufenen Reduzierer zu tun hat), verwenden Sie reduceReducers , was im Wesentlichen compose für Reduzierer ist (nun, es ist buchstäblich Komposition in einer (Action ->) Monade).

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen