Verwendung von Boilerplate-Aktionen und Reducern in Redux

9

Ich habe den weit verbreiteten Ratschlag der React-Entwicklung gelernt, indem ich zuerst die Komponente props mastering, den UI-Status in der Komponentenebene this.state kapselt und ihn selektiv durch den Komponentenbaum weitergibt. Es war eine erleuchtende Erfahrung. Ich habe die Kraft des statuslosen Ansichtsdesignmusters erkannt und fühle, dass ich mit diesen Techniken robuste und gut organisierte Ergebnisse erzielen konnte.

Ich versuche nun, eine kompliziertere Zustandsverwaltung mit redux zu integrieren. Aber während ich mich durch die Komplexität wühle und redux in meine Apps integriere, stelle ich mich den folgenden Beobachtungen gegenüber, wie sich mein Code entwickelt hat. Einige dieser Entwicklungen erscheinen vernünftig, aber andere lassen mich in Frage stellen, ob ich die Dinge richtig mache.

1) Action Creators als Verknüpfung von Geschäfts- und UI-Logik

Ich finde, dass ein Großteil der Logik, die zuvor in den React Lifecycle-Funktionen componentDidUpdate usw. und in onTouch/onPress implementiert wurde, nun in Action-Creators implementiert ist. Dies scheint eine positive Entwicklung zu sein, da es "alles an einem Ort" hält und Unit-Tests ermöglicht.

Frage : Ist es die beste Vorgehensweise, die Geschäftslogik in einem Netz von ziemlich komplexen Aktionserstellern zu konzentrieren?

2) Ausgehöhlt reducers

Als Konsequenz zu # 1 oben finde ich, dass sich meine reducers und ihre entsprechenden action -Objekte zu einer De-facto-Liste von Setter entwickelt haben, die wenig mehr tun, als den State Store mit den übergebenen Werten zu aktualisieren, auf diese Weise:

%Vor%

Ein großer Teil des Grundes dafür ist, dass reducers reine Funktionen sein sollen und daher bin ich in dem, was ich damit machen kann, eingeschränkt (z. B. keine asynchrone Verarbeitung). Darüber hinaus dürfen Reduzierer nur auf ihrem jeweiligen Teilabschnitt des Ladenzustands arbeiten. Angesichts der Tatsache, dass ein Großteil der Komplexität meiner App bereits in den Aktions-Erstellern liegt , ist es schwer zu rechtfertigen, willkürlich Komplexität in reducers zu migrieren, nur um sie "nützlich" aussehen zu lassen.

Frage : Ist es normal und akzeptabel, Boilerplate reducers zu haben, die lediglich als glorifizierte Setter für den Redux-Speicher-Zustand fungieren?

3) redux-thunk überall

Ich habe separat nach SO gefragt, warum redux-thunk sogar notwendig ist (im Gegensatz zum Aufruf von Standardaktions-Erstellern in asynchronen Callbacks / Utility-Funktionen). Ich wurde auf diese Antwort von Dan Abramov hingewiesen, die eine sehr zufriedenstellende Erklärung liefert (angesichts der Skalierbarkeit, des Servers) Side Rendering und Myraid andere Gründe).

Nachdem ich die Notwendigkeit von redux-thunk akzeptiert habe, finde ich, dass die meisten meiner Aktions-Ersteller asynchrone Aktionen ausführen müssen und Zugriff auf getState oder dispatch mehrere Änderungen am Status benötigen . Daher habe ich ' thunks ' ausgiebig zurückgegeben.

Frage: Ist es normal, dass sich eine Redux-Anwendung weitgehend auf die Ersteller von thunk ed-Aktionen verlässt und selten eine Standardobjektaktion direkt auslöst?

4) Redux als globale this.state

Im Endeffekt hat sich der Store redux meiner App so entwickelt, dass er einem globalen this.state ähnelt. Sie könnten sich den gesamten Anwendungsstatus in this.state in der äußersten Containerkomponente vorstellen, aber ohne das unvermeidliche Durcheinander, das mit dem Übergeben der genannten state durch verschachtelte Ebenen von props einhergeht, und eventuelle Änderungen der Komponentenbaum durch ein Ratten-Nest von Handler Funktionen.

Frage: Ist redux das richtige Tool für einen globalen Statusspeicher? Gibt es Alternativen, die sich mehr mit dem integrierten this.state von react verhalten, so dass ein globaler Anwendungsstatus über zustandslose reaktionsfähige Komponenten verteilt und von der gesamten Anwendung aus über eine zentralisierte Umgebung aktualisiert werden kann? Switchboard ', ohne das scheinbar endlose Netz von Boilerplate, Konstanten und switch -Anweisungen, die mit der Übernahme redux kommen?

5) Eine einzelne Aktion? Diese Follow-up-Frage ist von einem der geposteten Kommentare inspiriert.

Frage : Könnte man legitim (in aller Ernsthaftigkeit, nicht nur eklatant demonstrieren) Redux mit genau einem Aktionstyp verwenden?

Beispiel - Aktionsersteller:

%Vor%

Der einzige universelle Reduktionsfall:

%Vor%

Scheint mir, dass dies ein globales Bereichszustandsobjekt bereitstellen würde, das automatisch zu connect ed-Komponenten zugeordnet wird, und eines, das von allen Vorteilen des unveränderlichen Zustands profitiert und mit React props interoperabel ist. In diesem Schema wird dispatch effektiv zu einem globalen setState .

Hinweis - Bitte gehen Sie diese Frage nicht falsch - das ist sicherlich keine Kritik an redux .Als Lerner bin ich offensichtlich nicht in der Lage, eine Technologie zu beurteilen, die auf der Expertise von Tausenden und der Unterstützung von Millionen beruht. Ich habe keinen Zweifel an seinem Wert im richtigen Kontext.

Ich spüre nur den Geruch eines fragwürdigen Musters in meinem eigenen Code und frage mich, ob ich irgendetwas falsch mache oder ob ich das richtige Werkzeug für die Aufgabe verwende.

    
pscl 13.12.2017, 22:32
quelle

2 Antworten

6

Meine Antwort bezieht sich hauptsächlich auf meine eigene Erfahrung Redux zu lernen und sie professionell zu benutzen. Das Team, in dem ich war, ging den gleichen Weg von Setter-ähnlichen Aktionen und wechselte dann zu Aktionsnamen, die eher ereignisbasiert waren und beschreiben, was passiert war und nicht, was passieren sollte.

  

Frage: Ist es die beste Vorgehensweise, die Geschäftslogik in einem Netz von ziemlich komplizierten Handlungsentwicklern zu konzentrieren?

Dies hängt davon ab, wie Ihre Aktionen benannt sind. In deinem Fall sind deine Handlungen sehr verherrlichte Setter, also wird deine gesamte Geschäftslogik in Action Creators leben. Wenn Sie Ihre Aktionen eher als ereignisähnlich (beschreibend über was ist passiert ) statt als Setter definieren, werden Sie einige der Geschäftslogik in die Reduzierer verschieben und die Komplexität von Aktionserstellern entfernen , denn Ereignisaktionen fühlen sich auf verschiedenen Reduzierern natürlich wiederverwendbarer an. Wenn Sie Aktionen setzen, besteht die Tendenz, Setter-Aktionen zu haben, die nur mit einem Reduzierer interagieren, und mehr Setter-Aktionen zu erstellen, wenn Sie andere Reduzierer einbeziehen wollen.

Wenn Sie eine App für eine Schule haben und ein Schüler ausgeschlossen wird, senden Sie wahrscheinlich eine REMOVE_STUDENT und dann eine DELETE_GRADES_FOR_STUDENT Aktion. Wenn Ihre Aktion einen ereignisähnlichen Namen hat, neigen Sie eher dazu, eine STUDENT_EXPELLED -Aktion zu haben, bei der sowohl der Reducer als auch der Redner für die Rednerliste darauf reagieren.

Es gibt nichts, was Sie daran hindert, setterähnliche Namen zu haben und in mehreren Reduzierungen dagegen zu agieren. Es ist einfach nicht die Tendenz, auf die mein Team bei der Arbeit in Redux und Verwendung von Setter-ähnlichen Namen fiel. Wir wollten nicht die Erwartungen und die Reinheit verschleiern, die sich daraus ergaben, dass wir präzise Aktionsnamen hatten, deren Auswirkungen auf den Staat sehr klar waren. REMOVE_STUDENT_GRADES und DELETE_STUDENT_FROM_ROSTER haben sich gut angefühlt.

  

Frage: Ist es normal, und akzeptable Praxis, Boilerplate Reducer zu haben, die lediglich als glorifizierte Setter zum reduktiven Speicherzustand funktionieren?

Es ist normal, aber nicht unbedingt korrekt. So wuchs unsere Codebasis zunächst - wir hatten sogar Standards, um unsere Aktionen als RESET_... , SET_... , REMOVE_... , ADD_... , UPDATE... usw. zu benennen. Dies schien eine Weile zu funktionieren, bis wir zusammenkamen Fälle, in denen wir mehrere Reduzierungen benötigten, um sie nach einer einzigen Aktion zu aktualisieren.

Ihre Aktionen werden sich auf eine dieser beiden Arten entwickeln (oder beide)

  1. Verteilen Sie mehrere Aktionen in einer Zeile ( Sie müssen eine Bibliothek wie redux-batch-actions , wenn Sie mehrere Aktionen in einer Zeile ausführen möchten. Wir haben uns entschieden, dies nicht zu verwenden, weil es umständlich ist und nicht das Gefühl hatte, dass es sehr gut skaliert wird, da unsere Codebasis an Größe zunimmt.

  2. Benennen Sie Ihre Aktionen so um, dass sie allgemeiner und wiederverwendbar über verschiedene Reduzierungen sind. Das haben wir getan. Aktionen als Setter und Getter zu haben war umständlich. Dan Abramov und andere haben ihre Meinung geäußert, dass Redux-Aktionen events (eine Beschreibung einer Sache, die passiert ist) statt instructions (eine Beschreibung einer Sache, die passieren sollte) sein sollten. Das Team, mit dem ich arbeite, stimmte dem zu, und wir haben uns vom Setterstil der Aktionen entfernt. Es gab viel Debatte darüber früher, als Redux neu war.

In Szenario 1 können Sie Folgendes tun:

%Vor%

Wenn Sie diesen Weg ohne Batch-Aktionen gehen, ist das ein absoluter Albtraum. Jedes versendete Ereignis berechnet den Status neu und rendert Ihre App erneut. Das fällt sehr schnell auseinander.

%Vor%

Im obigen Code senden Sie eine Aktion, um den Schüler aus der Klasse zu entfernen, und dann rendert die App erneut. Wenn Sie eine Noten-Seite geöffnet haben, und Sie können die Schüler Noten sehen, aber der Schüler wird entfernt, werden Sie sehr wahrscheinlich Referenzstatus in der Studentenroster Reducer, um die Student Info zu greifen, und das kann / wird einen JS-Fehler werfen . Schlechte Nachrichten. Du hast die Noten für einen Schüler von undefined ?! Ich bin selbst auf solche Probleme gestoßen und es war Teil unserer Motivation, zu Option 2 weiterzugehen. Sie werden von diesen Arten von Zuständen, sogenannten "Zwischenzuständen", hören und sie sind problematisch.

In Szenario 2 sieht Ihr Code möglicherweise so aus:

%Vor%

Mit dem obigen Code wird der Schüler über die Aktion ausgeschlossen, und seine Daten werden von allen Reduzierern in einem Schritt entfernt. Das passt gut und Ihre Codebasis spiegelt die Geschäftsbedingungen Ihrer App wider, über die Sie täglich sprechen. Sie können auch die gleichen Statusaktualisierungen für mehrere Ereignisse durchführen, wenn dies aus Sicht der Geschäftslogik sinnvoll ist:

%Vor%
  

Frage: Ist es normal, dass sich eine Redux-Anwendung weitgehend auf Thunk-Ersteller von Aktionen verlässt und selten eine Standard-Objektaktion direkt auslöst?

Ja definitiv.Sobald Sie in einem Action-Creator ein kleines Stück Daten aus dem Store holen müssen, muss es ein Thunk werden. Thunks sind sehr häufig und sollten Teil der Redux-Bibliothek gewesen sein.

Als unsere Thunks immer komplexer wurden, wurden sie verwirrend und schwer zu verstehen. Wir haben begonnen, Versprechungen zu missbrauchen und Werte zurückzugeben, und es war anstrengend. Sie zu testen war auch ein Albtraum. Du musst alles ausspotten, es ist schmerzhaft.

Um dieses Problem zu lösen, haben wir redux-saga eingezogen. Redux-Saga ist leicht mit Bibliotheken wie redux-saga-test-plan oder redux-saga-test-engine (wir verwenden Test-Engine und bei Bedarf dazu beigetragen haben).

Wir sind nicht 100% sagas und wollen nicht sein. Wir verwenden immer noch Thunks nach Bedarf. Wenn Sie Ihre Aktion verbessern müssen, um ein wenig intelligenter zu werden, und der Code ist sehr einfach, gibt es keinen Grund, warum Sie diese Aktion nicht zu einem Thunk aktualisieren sollten.

Sobald ein Action-Creator komplex genug wird, um einen Unit-Test zu rechtfertigen, kann redux-saga nützlich sein.

Redux-Saga hat eine grobe Lernkurve und fühlt sich zunächst ziemlich bizarr an. Saga manuell zu testen ist miserabel. Große Lernerfahrung, aber ich würde es nie wieder tun.

  

Frage: Ist redux das richtige Werkzeug für einen globalen State Store? Gibt es Alternativen, die sich ähnlich verhalten wie der eingebaute This.State von reactive, der es ermöglicht, einen globalen Anwendungszustand durch zustandslose reaktive Komponenten zu propagieren und von der gesamten Anwendung über eine zentralisierte "Schalttafel" zu aktualisieren, ohne das scheinbar endlose Netz von Boilerplate, Konstanten und Schaltanweisungen, die mit adopting redux kommen?

MobX - Ich habe gute Dinge von Leuten gehört, die ähnliche Beschwerden haben Redux (zu viel Vortex, zu viele Dateien, alles ist getrennt) Ich benutze es nicht selbst und habe es auch nicht benutzt. Es besteht eine gute Chance, dass Sie es mehr genießen als Redux. Es löst das gleiche Problem, also wenn Sie es tatsächlich mehr genießen, dann kann es den Wechsel wert sein. Entwicklererfahrung ist sehr wichtig, wenn Sie lange an Code arbeiten werden.

Ich bin okay mit dem Redux-Text und was nicht. Das Team, an dem ich gearbeitet habe, hat Makros erstellt, um den Grundstein für das Erstellen neuer Aktionen zu legen, und wir haben viele Tests an Ort und Stelle, so dass unser Redux-Code ziemlich solide ist. Sobald Sie für eine Weile mit ihm arbeiten, verinnerlichen Sie den Textbaustein und es ist nicht so anstrengend.

Wenn Sie langfristig bei Redux bleiben und clever genug sind, auf redux Es ist ein großer Gewinn für die langfristige Wartbarkeit. Vollständig typisierte Redox-Code ist erstaunlich zu arbeiten, vor allem für das Refactoring. Es ist so einfach, einen Reducer / actionCreator zu refaktorieren, aber vergessen Sie nicht, den Unit Test Code zu aktualisieren. Wenn Ihre Komponententests vom Fluss erfasst werden, wird es sich beschweren, dass Sie eine Funktion sofort falsch aufrufen. Es ist wunderbar.

Introducing Flow ist eine komplexe Hürde, um darüber hinwegzukommen, aber es lohnt sich. Ich war nicht an der Erstinstallation beteiligt, und ich nehme an, dass es einfacher geworden ist, eine Codebasis einzuführen, aber ich könnte mir vorstellen, dass es einige Stunden und Lernen erfordern wird. Es lohnt sich aber. Auf jeden Fall 100% wert.

  

Frage: Könnte man legitim (in allem Ernst, nicht nur eklatant demonstrieren) redux mit genau einem Reduzierer verwenden?

Du könntest es definitiv, es könnte für eine kleine App funktionieren. Es würde nicht gut für ein größeres Team skalieren, und das Refactoring scheint ein Albtraum zu werden. Wenn Sie den Speicher in separate Reduzierungen aufteilen, können Sie Verantwortlichkeiten und Bedenken isolieren.

    
Cory Danielson 14.12.2017, 00:18
quelle
4

Ich bin ein Redux-Betreuer. Ich werde Ihnen einige erste Antworten geben und Sie auf einige Lernressourcen hinweisen, und ich kann weitere Fragen nach Bedarf beantworten.

Zuerst hat Cory Danielson einige ausgezeichnete Ratschläge gegeben, und ich möchte so ziemlich alles wiedergeben, was er gesagt hat.

Aktionsentwickler, Reducer und Geschäftslogik :

Ich zitiere den Redux-FAQ-Eintrag zum Aufteilen der Geschäftslogik zwischen Aktionserstellern und Reducern :

  

Es gibt keine eindeutige Antwort darauf, welche Teile der Logik in einen Reducer oder einen Action Creator passen sollten. Einige Entwickler bevorzugen "fette" Action-Ersteller, mit "dünnen" Reduzierern, die einfach die Daten in einer Aktion aufnehmen und sie blind in den entsprechenden Zustand verschmelzen. Andere versuchen, Aktionen so klein wie möglich zu halten und die Verwendung von getState () in einem Aktionsersteller zu minimieren. (Für diese Frage fallen andere asynchrone Ansätze wie Sagas und Observables in die Kategorie "action creator".)

     

Es gibt einige potenzielle Vorteile, wenn Sie Ihren Reduzierern mehr Logik hinzufügen. Es ist wahrscheinlich, dass die Aktionstypen semantischer und aussagekräftiger sind (z. B. "USER_UPDATED" anstelle von "SET_STATE"). Darüber hinaus bedeutet mehr Logik in Reduzierern, dass mehr Funktionalität durch Zeitreise-Debugging beeinflusst wird.

     

Dieser Kommentar fasst die Dichotomie gut zusammen:

     
    

Nun ist das Problem, was man in den Action Creator steckt und was im Reducer die Wahl zwischen fetten und dünnen Action-Objekten ist. Wenn Sie die gesamte Logik in den Aktionsersteller einfügen, erhalten Sie fette Aktionsobjekte, die im Grunde die Aktualisierungen für den Status deklarieren. Reduzierer werden rein, dumm, add-this, entfernen Sie das, aktualisieren Sie diese Funktionen. Sie werden leicht zu komponieren sein. Aber nicht viel von Ihrer Geschäftslogik wird da sein. Wenn Sie mehr Logik in den Reducer setzen, erhalten Sie nette, dünne Action-Objekte, die meisten Ihrer Datenlogik an einem Ort, aber Ihre Reducer sind schwieriger zu komponieren, da Sie möglicherweise Informationen von anderen Zweigen benötigen. Sie enden mit großen Reduzierern oder Reduzierern, die zusätzliche Argumente von oben nach oben nehmen.

  

Ich habe in meinem Beitrag mehr über dieses Thema gesprochen The Tao von Redux, Teil 2 - Praxis und Philosophie Anfang des Jahres (speziell die Abschnitte auf Aktionssemantik und dicke vs dünne Reduzierungen und wieder in ein Reddit-Kommentar-Thread .

Verwendung von Thunks:

Ja, thunks sind ein wertvolles Werkzeug für jede komplexe synchrone Logik, die außerhalb einer Komponente leben muss, einschließlich aller Codes, die Zugriff auf den aktuellen Geschäftszustand benötigen. Sie sind auch nützlich für einfache asynchrone Logik (wie grundlegende AJAX-Aufrufe mit nur Erfolg / Fehler-Handler). Ich diskutierte die Vor- und Nachteile von Thunks in meinem Beitrag Idiomatische Redux: Gedanken zu Thunks, Sagas, Abstraktion und Wiederverwendbarkeit .

In meiner eigenen App verwende ich Thunks an vielen Stellen, sagas auch für komplexere asynchrone Logik und Workflows und empfehle Thunks als nützliches Tool insgesamt.

Redux als globaler Speicher

Ich habe oft gesagt, dass Sie so viel oder so wenig Abstraktion auf Redux verwenden können, wie Sie wollen. Sie haben keine Um Switch-Anweisungen zu verwenden , können Sie Nachschlagetabellen oder jede andere bedingte Logik in Ihren Reduzierungen verwenden, und Sie sind sehr ermutigt, Reducer-Logik zu verwenden . In der Tat gibt es Dutzende vorhandener Dienstprogramme, um wiederverwendbare Aktionsersteller und Reduktoren zu generieren , sowie viele Abstraktionsbibliotheken höherer Ebene, die auf Redux geschrieben sind.

Verwendung eines einzelnen Blind-Setter-Reduzierstücks

Dies ist , und jemand anderes hatte einen guten Kommentar einen anderen Reddit-Thread . Es ist sicherlich technisch machbar, aber pro Reddit Kommentar, Ihre Reducer nicht Sie besitzen die Statusform nicht mehr, sondern die Ersteller von Aktionen, und nichts hindert sie daran, Daten einzugeben, die für einen bestimmten Aktionstyp oder Reduzierer keinen Sinn ergeben.

Wie ich in Das Tao von Redux, Teil 1 - Implementierung und Absicht , war eine der wichtigsten Absichten hinter der Erstellung von Redux, dass Sie in der Lage sein sollten, ein Protokoll der ausgeteilten Aktionen zu betrachten und zu verstehen, wo / wann / warum / wie wurde dein Status aktualisiert? Während es Redux selbst egal ist, was das action.type -Feld tatsächlich enthält, macht ein Action-History-Protokoll für Sie (oder einen anderen Entwickler) mehr Sinn, wenn die abgesetzten Aktionen einen sinnvollen Namen haben. Wenn Sie 10 "SET_STATE" actions in einer Zeile sehen, können Sie nichts Nützliches über das Geschehen erfahren, und während Sie den Inhalt jeder Aktion und das resultierende diff ansehen können, bedeutet ein Aktionstyp wie "EXPEL_STUDENT" a viel mehr nur durch das Lesen. Darüber hinaus können eindeutige Aktionstypen auch an bestimmten Stellen in der Codebasis nachverfolgt werden, wodurch Sie leichter erkennen können, woher die Daten stammen.

Hoffentlich hilft das bei der Beantwortung einiger Ihrer Fragen. Wenn Sie gerne weiter diskutieren möchten, stehe ich normalerweise in den Reactiflux-Chat-Kanälen auf Discord am Abend in den USA auf. Sei froh, dass du vorbeikommst und dich irgendwann unterhalten kannst!

    
markerikson 14.12.2017 00:40
quelle