Redux: Was sind die Nachteile, wenn Sie Ihren gesamten Zustand in einem einzigen unveränderlichen Atom speichern?

Erstellt am 10. Feb. 2016  ·  91Kommentare  ·  Quelle: reduxjs/redux

Ich verstehe, dass dies das Prinzip ist, das allen Redux zugrunde liegt, und dass es mit all diesen fantastischen Vorteilen verbunden ist, die mittlerweile ziemlich bekannt sind.

Ich bin jedoch der Meinung, dass ein Punkt, an dem Redux fehlt, darin besteht, dass die konzeptionellen Nachteile der Verwendung dieser Architektur nicht offen beschrieben werden. Vielleicht ist dies eine gute Wahl, da Sie möchten, dass die Leute nicht wegen der Negative zurückschrecken.

Ich bin nur neugierig, weil es nicht nur für Redux gilt, sondern in gewisser Weise auch für andere Dinge wie Om , Datomic und das die Datenbank auf den Kopf zu stellen . Ich mag Redux, ich mag Om, ich interessiere mich für Datomic und ich mochte dieses Gespräch wirklich.

Aber bei all der Suche, die ich durchgeführt habe, ist es schwierig, Leute zu finden, die kritisch gegenüber dem einzelnen unveränderlichen Geschäft sind. Dies ist ein Beispiel, aber es scheint eher ein Problem mit der Ausführlichkeit von Redux als mit der tatsächlichen Architektur zu haben.

Das einzige, woran ich denken kann, ist, dass es möglicherweise mehr Speicher benötigt, um Kopien Ihres Zustands zu speichern, oder dass es etwas schwieriger ist, mit Redux schnell Prototypen zu erstellen.

Könnt ihr mir helfen, das für mich zu erklären, weil ihr wahrscheinlich viel mehr darüber nachgedacht habt als ich?

discussion docs question

Hilfreichster Kommentar

Redux ist im Grunde genommen Event-Sourcing, bei dem nur eine Projektion verwendet werden muss.

In verteilten Systemen gibt es im Allgemeinen ein Ereignisprotokoll (wie Kafka) und mehrere Verbraucher, die dieses Protokoll in mehrere Datenbanken / Speicher projizieren / reduzieren können, die auf verschiedenen Servern gehostet werden (normalerweise ist ein DB-Replikat tatsächlich ein Reduzierer). In einer verteilten Welt ist die Last- und Speichernutzung also ... verteilt, während in Redux, wenn Sie Hunderte von Widgets haben, die alle ihren lokalen Status haben, all dies in einem einzigen Browser ausgeführt wird und jede Statusänderung aufgrund von unveränderlichem Status einen gewissen Overhead hat Datenaktualisierungen.

In den meisten Fällen ist dieser Overhead keine große Sache. Wenn Sie jedoch Texteingaben in einen nicht so guten Zustand bringen und auf einem langsamen Mobilgerät schnell tippen, ist dies nicht immer leistungsfähig genug.

Das Rendern dieses Status von ganz oben ist meiner Erfahrung nach nicht bequem und auch nicht immer leistungsfähig genug (zumindest mit React, das wahrscheinlich nicht die schnellste VDom-Implementierung ist). Redux löst dieses Problem mit connect aber mit zunehmender Anzahl von Verbindungen kann es dennoch zu einem Engpass kommen. Es gibt Lösungen

Auch persistente Datenstrukturen wie ImmutableJS bieten bei einigen Vorgängen nicht immer die besten Leistungen, z. B. das Hinzufügen eines Elements an einem zufälligen Index in einer großen Liste (siehe meine Erfahrung beim Rendern großer Listen hier ).

Redux enthält sowohl das Ereignisprotokoll als auch die Projektion, da dies praktisch und für die meisten Anwendungsfälle in Ordnung ist. Es kann jedoch möglich sein, ein Ereignisprotokoll außerhalb von Redux zu verwalten und es in zwei oder mehr Redux-Speicher zu projizieren. Fügen Sie alle diese Redux-Speicher hinzu Reagieren Sie auf den Kontext unter verschiedenen Schlüsseln und haben Sie weniger Overhead, indem Sie angeben, zu welchem ​​Speicher wir eine Verbindung herstellen möchten. Dies ist absolut möglich, würde jedoch die Implementierung von API und Devtools erschweren, da Sie jetzt eine klare Unterscheidung zwischen dem Store und dem Ereignisprotokoll benötigen.


Ich stimme auch @jquense zu

Die Vorteile einer einfachen Flüssigkeitszufuhr, Schnappschüssen, Zeitreisen usw. funktionieren nur, wenn es keinen anderen Ort gibt, an dem ein wichtiger Zustand lebt. Im Kontext von React bedeutet dies, dass Sie den Status speichern müssen, der ordnungsgemäß zu den Komponenten im Store gehört, oder eine Reihe von Vorteilen verlieren müssen. Wenn Sie alles in Redux einfügen möchten, wird es oft lästig und ausführlich und fügt eine ärgerliche Indirektionsebene hinzu.

Für die Montage eines beliebigen Status im Redux-Speicher ist mehr Boilerplate erforderlich. Die Ulmenarchitektur löst dies wahrscheinlich auf elegantere Weise, erfordert aber auch viel Boilerplate.

Es ist aber auch nicht möglich oder performant, alle Zustände kontrolliert zu machen. Manchmal verwenden wir vorhandene Bibliotheken, für die es schwierig ist, eine deklarative Schnittstelle zu erstellen. Einige Zustände sind auch schwer effizient auf Redux-Speicher zu montieren, einschließlich:

  • Texteingaben
  • Bildlaufposition
  • Ansichtsfenstergröße
  • Mausposition
  • Caret Position / Auswahl
  • Canvas dataUrls
  • Unserialisierbarer Zustand
  • ...

Alle 91 Kommentare

Das ist eine gute Frage. Ich werde versuchen, morgen eine umfassende Antwort zu schreiben, aber ich würde gerne zuerst von einigen unserer Benutzer hören. Es wäre schön, diese Diskussion später als offizielle Dokumentseite zusammenzufassen. Das haben wir zunächst nicht gemacht, weil wir die Nachteile nicht kannten.

Vielen Dank! Ich wollte wirklich davon hören.

Übrigens ermutige ich alle, die zu dieser Diskussion beitragen möchten, auch eine Vorstellung davon zu geben, was sie mit Redux aufgebaut haben, damit der Umfang der Projekte leichter zu verstehen ist.

Eine Art schwierige Frage, die B / C-Projekte wie OM und Redux sowie andere Atom-Bibliotheken mit einem einzigen Staat stellen, mildern die Nachteile aktiv ab. Ohne die Unveränderlichkeitsbeschränkung und den kontrollierten Zugriff ist ein einzelnes Zustandsatom keineswegs anders als das Anhängen aller Ihrer Daten an das window (von dem die Nachteile bekannt sind).

Speziell für den Redux-Ansatz ist der größte Nachteil für uns jedoch, dass Einzelzustandsatome so etwas wie alles oder nichts sind. Die Vorteile von einfacher Flüssigkeitszufuhr, Schnappschüssen, Zeitreisen usw. funktionieren nur, wenn es an keinem anderen Ort einen wichtigen Zustand gibt. Im Kontext von React bedeutet dies, dass Sie den Status speichern müssen, der ordnungsgemäß zu den Komponenten im Store gehört, oder eine Reihe von Vorteilen verlieren müssen. Wenn Sie alles in Redux einfügen möchten, wird es oft lästig und ausführlich und fügt eine ärgerliche Indirektionsebene hinzu.

Keiner dieser Nachteile war für uns jedoch besonders unerschwinglich :)

Also habe ich Redux und diesen Architekturstil in Augenschein genommen, um Spielzustände zu modellieren. Ich habe ein Interesse daran, simulierte Welten im Civ-Stil und vollständig aufgezeichnete Brettspielgeschichten einfacher in einem Browserkontext zu erstellen, was für manche vielleicht ein ungewöhnlicher Anwendungsfall ist, aber ich bezweifle, dass irgendjemand möchte, dass ich aufhöre.

In diesem Zusammenhang bin ich an Bemühungen interessiert, die Größe der Verlaufsdatenstruktur zu verwalten, Teile davon auf den Server oder die Festplatte zu verschieben, um sie zu speichern, bestimmte Segmente wiederherzustellen usw.

Das, womit ich als neuer Benutzer am meisten zu kämpfen habe, ist der Versuch, sowohl die extreme (imho) Änderung des Codierungsstils in Redux als auch die konzeptionellen Änderungen gleichzeitig zu überwinden. es bleibt auch jetzt noch ein bisschen entmutigend, einen Monat und ändert sich, nachdem man versucht hat, tief in es einzutauchen; Ich schaue als nächstes in https://github.com/ngrx/store nach, um zu den Konzepten zu gelangen, aber mit einem bekannteren Stil / einer bekannteren Syntax und mehr vom Rest des implizierten / bereitgestellten Frameworks.

Ich verstehe, dass ein Rahmen für manche eine Krücke ist; aber einige von uns brauchen diese; Nur wenige Leute werden an der Spitze ihres Spiels stehen, um nützliche, starke Meinungen zu haben. Ein Framework ist also besser, als im Dunkeln herumzufummeln, weißt du? Das ist ein Punkt von mir, einem Programmierer mittlerer Stufe mittleren Alters, der neu in den Konzepten ist :)

Grundsätzlich haben Redux und Ihre Videos mir beigebracht, wie man es in rohem Javascript macht, aber als weniger erfahrener Programmierer habe ich immer noch keine Ahnung, wie ich ein Produkt ohne weitere Anleitung liefern kann. Bis ich das Beispiel einer fertigen Sache sehe, habe ich es Steh einfach herum wie:

Nicht deine Schuld, aber immer noch ein Problem, das ich gerne lösen würde :)

Ich denke, die größte Frage, die ich derzeit noch habe, ist, wohin nicht serialisierbare Elemente wie Funktionen, Instanzen oder Versprechen gehen sollen. Ich habe mich neulich in Reactiflux darüber gewundert und keine guten Antworten bekommen. Ich habe auch gerade jemanden gesehen, der http://stackoverflow.com/questions/35325195/should-i-store-function-references-in-redux-store gepostet hat .

Anwendungsfall für mehrere Geschäfte (denken Sie daran, dies ist der beste, den ich mir vorstellen kann):

Eine App, die mehrere Unter-Apps miteinander kombiniert. Denken Sie an etwas wie My Yahoo oder ein anderes anpassbares Homepage-Produkt. Jedes Widget muss seinen eigenen Status beibehalten, ohne dass sich die beiden überschneiden. Jedem Widget ist wahrscheinlich ein Produktteam zugeordnet, sodass es möglicherweise die Auswirkungen des Codes anderer auf die Umgebung nicht kennt.

Zusammengenommen können Sie dies wahrscheinlich in Redux erreichen, indem Sie gegen einige Regeln verstoßen und darauf achten, diese Speicher in einem ähnlichen Kontext wie React zu verbreiten. Mit einem Framework, das standardmäßig mehrere Zustandsatome verarbeitet, ist dies möglicherweise einfacher.

@gaearon wir bauen Handelsplattform. Ich habe Ihnen bereits einmal erklärt, warum ein Geschäft nicht zu uns passt (nach dem Treffen von React.j in Sankt Petersburg). Ich werde versuchen, es noch einmal im Detail zu erklären (im Blog-Beitrag auf Medium?), Aber ich brauche wahrscheinlich Hilfe aufgrund meiner Englischkenntnisse :) Ist es in Ordnung, wenn ich es Ihnen oder jemand anderem hier zur Überprüfung in Twitter direkt sende Nachrichten, wann es fertig sein wird? Ich kann das genaue Datum zwar nicht nennen, aber ich werde versuchen, diesen Beitrag bald zu schreiben (höchstwahrscheinlich Ende dieses Monats).

Und ja, wir verwenden Redux immer noch mit mehreren Filialen, indem wir gegen einige Regeln aus Dokumenten verstoßen , wie react-redux so bequem verwenden, wenn wir Daten aus verschiedenen Filialen benötigen, wie sie sind im Falle eines Einzelgeschäfts)

Redux ist im Grunde genommen Event-Sourcing, bei dem nur eine Projektion verwendet werden muss.

In verteilten Systemen gibt es im Allgemeinen ein Ereignisprotokoll (wie Kafka) und mehrere Verbraucher, die dieses Protokoll in mehrere Datenbanken / Speicher projizieren / reduzieren können, die auf verschiedenen Servern gehostet werden (normalerweise ist ein DB-Replikat tatsächlich ein Reduzierer). In einer verteilten Welt ist die Last- und Speichernutzung also ... verteilt, während in Redux, wenn Sie Hunderte von Widgets haben, die alle ihren lokalen Status haben, all dies in einem einzigen Browser ausgeführt wird und jede Statusänderung aufgrund von unveränderlichem Status einen gewissen Overhead hat Datenaktualisierungen.

In den meisten Fällen ist dieser Overhead keine große Sache. Wenn Sie jedoch Texteingaben in einen nicht so guten Zustand bringen und auf einem langsamen Mobilgerät schnell tippen, ist dies nicht immer leistungsfähig genug.

Das Rendern dieses Status von ganz oben ist meiner Erfahrung nach nicht bequem und auch nicht immer leistungsfähig genug (zumindest mit React, das wahrscheinlich nicht die schnellste VDom-Implementierung ist). Redux löst dieses Problem mit connect aber mit zunehmender Anzahl von Verbindungen kann es dennoch zu einem Engpass kommen. Es gibt Lösungen

Auch persistente Datenstrukturen wie ImmutableJS bieten bei einigen Vorgängen nicht immer die besten Leistungen, z. B. das Hinzufügen eines Elements an einem zufälligen Index in einer großen Liste (siehe meine Erfahrung beim Rendern großer Listen hier ).

Redux enthält sowohl das Ereignisprotokoll als auch die Projektion, da dies praktisch und für die meisten Anwendungsfälle in Ordnung ist. Es kann jedoch möglich sein, ein Ereignisprotokoll außerhalb von Redux zu verwalten und es in zwei oder mehr Redux-Speicher zu projizieren. Fügen Sie alle diese Redux-Speicher hinzu Reagieren Sie auf den Kontext unter verschiedenen Schlüsseln und haben Sie weniger Overhead, indem Sie angeben, zu welchem ​​Speicher wir eine Verbindung herstellen möchten. Dies ist absolut möglich, würde jedoch die Implementierung von API und Devtools erschweren, da Sie jetzt eine klare Unterscheidung zwischen dem Store und dem Ereignisprotokoll benötigen.


Ich stimme auch @jquense zu

Die Vorteile einer einfachen Flüssigkeitszufuhr, Schnappschüssen, Zeitreisen usw. funktionieren nur, wenn es keinen anderen Ort gibt, an dem ein wichtiger Zustand lebt. Im Kontext von React bedeutet dies, dass Sie den Status speichern müssen, der ordnungsgemäß zu den Komponenten im Store gehört, oder eine Reihe von Vorteilen verlieren müssen. Wenn Sie alles in Redux einfügen möchten, wird es oft lästig und ausführlich und fügt eine ärgerliche Indirektionsebene hinzu.

Für die Montage eines beliebigen Status im Redux-Speicher ist mehr Boilerplate erforderlich. Die Ulmenarchitektur löst dies wahrscheinlich auf elegantere Weise, erfordert aber auch viel Boilerplate.

Es ist aber auch nicht möglich oder performant, alle Zustände kontrolliert zu machen. Manchmal verwenden wir vorhandene Bibliotheken, für die es schwierig ist, eine deklarative Schnittstelle zu erstellen. Einige Zustände sind auch schwer effizient auf Redux-Speicher zu montieren, einschließlich:

  • Texteingaben
  • Bildlaufposition
  • Ansichtsfenstergröße
  • Mausposition
  • Caret Position / Auswahl
  • Canvas dataUrls
  • Unserialisierbarer Zustand
  • ...

Diese Frage habe ich gerade auf SO gesehen:

http://stackoverflow.com/questions/35328056/react-redux-should-all-component-states-be-kept-in-redux-store

Echos einige Verwirrung Ich habe über die Verwaltung des UI-Status gesehen, und ob der UI-Status zur Komponente gehören oder in den Laden gehen sollte. # 1287 bietet eine gute Antwort auf diese Frage, ist aber zunächst nicht so offensichtlich und kann zur Debatte stehen.

Darüber hinaus kann dies ein Hindernis sein, wenn Sie versuchen, so etwas wie https://github.com/ericelliott/react-pure-component-starter zu implementieren, bei dem jede Komponente rein ist und in ihrem eigenen Zustand kein Mitspracherecht hat .

Ich fand es schwierig, den einzelnen Statusbaum von redux zu verwenden, wenn ich die Daten mehrerer Sitzungen verwalten musste. Ich hatte eine beliebige Anzahl von Zweigen mit identischer Form und jeder hatte mehrere eindeutige Bezeichner (je nachdem, woher die Daten kamen, würden Sie einen anderen Schlüssel verwenden). Die Einfachheit der Reduzierungsfunktionen und der Funktionen zur erneuten Auswahl verschwand angesichts dieser Anforderungen schnell - es mussten benutzerdefinierte Getter / Setter geschrieben werden, um auf einen bestimmten Zweig abzuzielen, der sich in einer ansonsten einfachen und kompatiblen Umgebung übermäßig komplex und flach anfühlte. Ich weiß nicht, ob mehrere Geschäfte die beste Option sind, um diese Anforderung zu erfüllen, aber einige Tools zum Verwalten von Sitzungen (oder anderen identisch geformten Daten) in einem einzigen Statusbaum wären hilfreich.

Erhöhte Wahrscheinlichkeit von Kollisionen von Statusschlüsseln zwischen Reduzierern.
Datenmutationen und -deklarationen sind weit entfernt von dem Code, in dem die Daten verwendet werden (wenn wir einen Code schreiben, versuchen wir, Variablendeklarationen in der Nähe des Ortes zu platzieren, an dem wir sie verwenden).

Lassen Sie mich meinen speziellen Anwendungsfall vorwegnehmen: Ich verwende Redux mit Virtual-Dom, bei dem alle meine UI-Komponenten rein funktional sind und es keine Möglichkeit gibt, einen lokalen Status für verschiedene Komponenten zu haben.

Vor diesem Hintergrund ist es definitiv am schwierigsten, den Animationszustand zu binden. Die folgenden Beispiele beziehen sich auf den Animationsstatus, aber ein Großteil davon kann auf den UI-Status verallgemeinert werden.

Einige Gründe, warum der Animationsstatus für Redux unangenehm ist:

  • Animationsdaten ändern sich häufig
  • Animationsspezifische Aktionen verschmutzen den Verlauf von Aktionen in Ihrer App und machen es schwierig, Aktionen auf sinnvolle Weise zurückzusetzen
  • Der Redux-Statusbaum beginnt, den Komponentenbaum zu spiegeln, wenn viele komponentenspezifische Animationen vorhanden sind
  • Selbst grundlegende Animationszustände wie animationStarted oder animationStopped beginnen, den Zustand mit Ihrer Benutzeroberfläche zu koppeln.

Auf der anderen Seite gibt es definitiv Herausforderungen beim Erstellen des Animationsstatus vollständig außerhalb des Redux-Statusbaums.

Wenn Sie versuchen, Animationen selbst zu erstellen, indem Sie das DOM manipulieren, müssen Sie auf Folgendes achten:

  • Virtual-Dom-Unterschiede berücksichtigen Ihre Animationsmanipulationen nicht, z. B. benutzerdefinierte Stile, die Sie auf dem Knoten festgelegt haben. Wenn Sie einige Stile auf einem DOM-Knoten festlegen, müssen Sie sie selbst entfernen, wenn Sie möchten, dass sie nicht mehr angezeigt werden
  • Wenn Sie Animationen für das Dokument selbst ausführen, müssen Sie sich der Aktualisierungen von virtuellen Domänen sehr bewusst sein. Sie können eine Animation auf einem DOM-Knoten starten, nur um festzustellen, dass der Inhalt dieses DOM-Knotens während der Animation geändert wurde
  • Wenn Sie Animationen für das Dokument selbst ausführen, müssen Sie vorsichtig sein, wenn Sie auf Ihrem Knoten festgelegte Stile und Attribute für Virtual-Dom überschreiben, und Sie müssen vorsichtig sein, wenn Sie Stile und Attribute überschreiben, die von Virtual-Dom festgelegt wurden

Und wenn Sie versuchen, Virtual-Dom für alle DOM-Manipulationen sorgen zu lassen (was Sie sollten!), Ohne jedoch den Animationsstatus in Redux beizubehalten, treten folgende schwierige Probleme auf:

  • Wie setzen Sie den Animationsstatus Ihren Komponenten zur Verfügung? Lokaler Zustand in Ihren Komponenten? Ein anderer globaler Staat?
  • Normalerweise befindet sich Ihre Statuslogik in Redux-Reduzierern, aber jetzt müssen Sie Ihren Komponenten eine Menge Renderlogik direkt hinzufügen, damit sie als Reaktion auf Ihren Status animiert werden. Dies kann sehr herausfordernd und ausführlich sein.

Es gibt derzeit großartige Projekte wie React-Motion, die große Fortschritte bei der Lösung dieses Problems für React machen, aber Redux ist nicht exklusiv für React. Ich denke auch nicht, dass es so sein sollte - viele Leute bringen ihre eigene Ansichtsebene mit und versuchen, sich in Redux zu integrieren.

Für alle, die neugierig sind, besteht die beste Lösung, die ich für Redux + virtual-dom gefunden habe, darin, zwei Statusatome beizubehalten: Redux behält den Kernanwendungsstatus bei, enthält die Kernlogik, um diesen Status als Reaktion auf Aktionen usw. zu manipulieren. Der andere Status ist ein veränderliches Objekt, das den Animationsstatus enthält (ich verwende mobservable). Der Trick besteht dann darin, sowohl Änderungen des Redux-Status als auch des Animationsstatus zu abonnieren und die Benutzeroberfläche als Funktion von beiden zu rendern:

/* Patch h for jsx/vdom to convert <App /> to App() */
import h from './h'
import { diff, patch, create } from 'virtual-dom'
import { createStore } from 'redux'
import { observable, autorun } from 'mobservable'
import TWEEN from 'tween.js'
import rootReducer from './reducers'
import { addCard } from './actions'
import App from './containers/App'

// Redux state
const store = createStore()

// Create vdom tree
let tree = render(store.getState())
let rootNode = create(tree)
document.body.appendChild(rootNode)

// Animation observable
let animationState = observable({
  opacity: 0
})

// Update document when Redux state 
store.subscribe(function () {
  // ... anything you need to do in response to Redux state changes
  update()
})

// Update document when animation state changes
autorun(update)

// Perform document update with current state
function update () {
  const state = store.getState()
  let newTree = render(state, animationState)
  let patches = diff(tree, newTree)
  rootNode = patch(rootNode, patches)
  tree = newTree
}

// UI is a function of current state (and animation!)
function render (state, animation = {}) {
  return (
    <App {...state} animation={animationState} />
  )
}

// Do some animations
function animationLoop (time) {
  window.requestAnimationFrame(animationLoop)
  TWEEN.update(time)
}
animationLoop()

new TWEEN.Tween(animationState)
      .to({ opacity: 100 }, 300)
      .start()

// Or when you dispatch an action, also kick off some animation changes...
store.dispatch(addCard())
/* etc... */

Vielen Dank für die tollen Antworten! Lass sie kommen.

Eine Sache zu beachten ist, dass wir nicht beabsichtigen, Redux für _all_ state zu verwenden. Was auch immer für die App wichtig erscheint. Ich würde argumentieren, dass Eingaben und Animationsstatus von React (oder einer anderen kurzlebigen Statusabstraktion) behandelt werden sollten. Redux funktioniert besser für Dinge wie abgerufene Daten und lokal geänderte Modelle.

@taggartbg

Ich fand es schwierig, den einzelnen Statusbaum von redux zu verwenden, wenn ich die Daten mehrerer Sitzungen verwalten musste. Ich hatte eine beliebige Anzahl von Zweigen mit identischer Form und jeder hatte mehrere eindeutige Bezeichner (je nachdem, woher die Daten kamen, würden Sie einen anderen Schlüssel verwenden). Die Einfachheit der Reduzierungsfunktionen und der Funktionen zur erneuten Auswahl verschwand angesichts dieser Anforderungen schnell - es mussten benutzerdefinierte Getter / Setter geschrieben werden, um auf einen bestimmten Zweig abzuzielen, der sich in einer ansonsten einfachen und kompatiblen Umgebung übermäßig komplex und flach anfühlte.

Würde es Ihnen etwas ausmachen, ein Problem zu erstellen, das Ihren Anwendungsfall detaillierter beschreibt? Möglicherweise gibt es eine einfache Möglichkeit, die fehlende Statusform anders zu organisieren. Im Allgemeinen sind mehrere Zweige mit derselben Zustandsform, die jedoch von verschiedenen Reduzierern verwaltet werden, ein Anti-Muster.

@istarkov

Erhöhte Wahrscheinlichkeit von Kollisionen von Statusschlüsseln zwischen Reduzierern.

Würde es Ihnen etwas ausmachen, genauer zu erklären, wie dies geschieht? Normalerweise empfehlen wir Ihnen, nur einen einzigen Reduzierer für eine Statusscheibe auszuführen. Wie können Kollisionen passieren? Machst du mehrere Durchgänge über den Staat? Wenn ja warum?

@gaearon @istarkov : Vielleicht ist gemeint, dass verschiedene Plugins und verwandte Bibliotheken um denselben Namespace der obersten Ebene drängeln? Bibliothek A möchte einen Top-Key mit dem Namen "myState", Bibliothek B usw. jedoch auch.

Ja, das ist ein guter Punkt, auch wenn @istarkov das nicht gemeint hat. (Ich weiß es nicht.) Im Allgemeinen sollten Bibliotheken eine Möglichkeit bieten, Reduzierer an einer beliebigen Stelle im Baum anzubringen, aber ich weiß, dass einige Bibliotheken dies nicht zulassen.

@gaearon

Würde es Ihnen etwas ausmachen, ein Problem zu erstellen, das Ihren Anwendungsfall detaillierter beschreibt? Möglicherweise gibt es eine einfache Möglichkeit, die fehlende Statusform anders zu organisieren. Im Allgemeinen sind mehrere Zweige mit derselben Zustandsform, die jedoch von verschiedenen Reduzierern verwaltet werden, ein Anti-Muster.

Ich würde gerne! Ich werde das tun, wenn ich einen Moment Zeit habe.

Ich denke, es wird definitiv als Antimuster angesehen, obwohl ich einen einzigen, gemeinsamen Reduzierer habe, um diese Zweige zu verwalten. Dennoch denke ich, dass das Paradigma, das Redux bietet, nicht weit davon entfernt ist, ein perfekter Anwendungsfall für die Serialisierung / Deserialisierung identischer Zweige als unveränderlichen Zustand zu sein. Ich verstehe nicht, warum es gegen das Ethos von Redux verstoßen würde, so etwas wie eine Neuauswahl mit einer angenommenen Logik zu erstellen, um bestimmte Zweige mit einem Schlüssel anzusprechen.

Ich würde mich gerne außerhalb des Threads darüber unterhalten, ich könnte mich sehr gut irren.

@taggartbg : Sprechen Sie ganz ohne zu wissen, wie Ihr Code hier aussieht. Sprechen Sie über den Versuch, mit einem Zustand umzugehen, der so aussieht?

{ groupedData : { first : {a : 1, b : 2}, second : {a : 3, b : 4}, third : {a : 5, b, 6} }

Es scheint, als könnten Sie auf der Reduziererseite damit umgehen, indem Sie eine einzige Reduzierungsfunktion haben, die den ID-Schlüssel pro Gruppe als Teil jeder Aktion verwendet. Haben Sie sich tatsächlich etwas wie https://github.com/erikras/multireducer , https://github.com/lapanoid/redux-delegator , https://github.com/reducks/redux-multiplex oder angesehen https://github.com/dexbol/redux-register?

Auf der Seite der erneuten Auswahl kann auch die Tatsache hilfreich sein, dass React-Redux jetzt Selektoren pro Komponente unterstützt, da dies Szenarien verbessert, in denen Sie eine Auswahl basierend auf Komponentenrequisiten durchführen.

@gaearon

Eine Sache zu beachten ist, dass wir nicht beabsichtigen, Redux für alle Zustände zu verwenden. Was auch immer für die App wichtig erscheint. Ich würde argumentieren, dass Eingaben und Animationsstatus von React (oder einer anderen kurzlebigen Statusabstraktion) behandelt werden sollten. Redux funktioniert besser für Dinge wie abgerufene Daten und lokal geänderte Modelle.

Ein React-Entwickler, der die Dokumente und das Tutorial liest, verfügt bereits über diese kurzlebige Zustandsabstraktion. Daher ist es möglich, dass dies bereits berücksichtigt wird, wenn er überlegt, wo Redux für ein Projekt sinnvoll ist.

Aus der Sicht eines Entwicklers mit wenig oder keiner Erfahrung mit React, der einen funktionalen Ansatz für die Benutzeroberfläche verfolgen möchte, ist es jedoch möglicherweise nicht offensichtlich, welcher Status zu Redux gehört. Ich habe jetzt mehrere Projekte durchgeführt, bei denen zunächst Redux und Virtual-Dom ausreichten, aber als die Anwendung immer komplexer wurde, wurde diese andere "kurzlebige Zustandsabstraktion" notwendig. Dies ist nicht immer offensichtlich, wenn Sie zum ersten Mal einen Animationsreduzierer mit einigen grundlegenden Animationsflags hinzufügen, wird aber später zu einem ziemlichen Schmerz.

Es könnte für die Dokumente hilfreich sein, zu erwähnen, welcher Status für Redux geeignet ist und welcher Status durch andere Tools besser gelöst werden kann. Es kann für React-Entwickler redundant sein, kann aber für andere Entwickler von großem Vorteil sein, wenn sie sich Redux ansehen und ihre Anwendungsarchitektur planen.

EDIT: Der Titel der Ausgabe lautet "Was sind die Nachteile der Speicherung Ihres gesamten Zustands in einem einzigen unveränderlichen Atom?" Dieser "Ihr gesamter Zustand in einem einzigen unveränderlichen Atom" ist genau die Art von Formulierung, die dazu führt, dass im Redux-Baum viele falsche Zustände auftreten. Die Dokumentation könnte einige explizite Beispiele enthalten, um Entwicklern dabei zu helfen, diese Art von Falle zu vermeiden.

@gaearon Wir hatten auf Cycle.js ein interessantes Gespräch über die architektonischen Unterschiede zwischen einem einzelnen Atomzustand und Pipping. HIER .

Ich weiß, dass dies nicht zu 100% mit Redux zusammenhängt, wollte aber eine andere Perspektive als eine Implementierung bieten, die Observable-Streams als Datenfluss um eine App verwendet (für Cycle ist die App eine Reihe reiner Funktionen).

Da alles in Cycle ein Observable ist, hatte ich Schwierigkeiten, den Zustand von einer Routenänderung zu einer anderen zu bewegen. Dies lag daran, dass der Status "Observable" keinen Abonnenten hatte, sobald eine Route geändert wurde, und überlegte, warum nicht ein einzelner Atomstatus implementiert werden sollte. Jedes Mal, wenn sich der Status in meiner App änderte, wurde er an den Speicher der obersten Ebene zurückgemeldet, wobei alle Rohrleitungen umgangen wurden ( Ansicht schrecklich) Zeichnungen hier ).

Bei Cycle müssen Sie normalerweise diese Schleife vom Top-Level-Treiber zur Komponente und zurück ausführen, da Nebenwirkungen in Treibern auftreten. Deshalb wollte ich das einfach überspringen und direkt wieder nach oben zu den Komponenten gehen, die zuhören. Ich nahm Redux als Inspirationsquelle.

Es ist kein Fall, der entweder richtig oder falsch ist, aber jetzt mache ich Piping und wir haben einen Zustandstreiber herausgefunden. Die Fähigkeit, meinen Zustand nach Bedarf an verschiedene Komponenten weiterzuleiten, ist wirklich flexibel für Refactoring, Prototyping und starkes Verständnis jede Staatsquelle, jeder Verbraucher und wie sie zueinander kamen.

Auch Neulinge in der Anwendung (wenn sie Cycle natürlich verstehen) können die Punkte einfach verbinden und sehr schnell eine visuelle Darstellung davon zeichnen, wie der Status behandelt und weitergeleitet wird und all dies aus dem Code.

Entschuldigung im Voraus, wenn dies völlig aus dem Zusammenhang geraten ist, wollte zeigen, wie ein anderes Paradigma ein ähnliches Gespräch geführt hat: smiley:

@ Timdorr

Jedes Widget muss seinen eigenen Status beibehalten, ohne dass sich die beiden überschneiden. Jedem Widget ist wahrscheinlich ein Produktteam zugeordnet, sodass es möglicherweise die Auswirkungen des Codes anderer auf die Umgebung nicht kennt.

Ich denke, es ist besser, wenn das Widget einen eigenen Status hat (vielleicht sogar einen eigenen (Redux?) Speicher), damit es in jeder (Mashup-) App eigenständig arbeiten und nur einige Eigenschaften erhalten kann. Denken Sie an das Wetter-Widget. Es kann Daten selbst abrufen und anzeigen und nur Eigenschaften wie Stadt, Höhe und Breite empfangen. Ein weiteres Beispiel ist das Twitter-Widget.

Tolle Diskussion!

Hauptsächlich finde ich es unpraktisch, mehrere Apps / Komponenten / Plugins zu kombinieren.

Ich kann ein Modul, das ich erstellt habe, nicht einfach in ein anderes Modul / eine andere App werfen, da ich seine Reduzierungen separat in den Store der App importieren muss und es unter einen bestimmten Schlüssel legen muss, den ich habe Modul weiß über. Dies hindert mich auch daran, das Modul zu duplizieren, wenn ich @connect , da es eine Verbindung zum gesamten Status herstellt.

Zum Beispiel:

Ich baue einen Messenger, der wie iMessage aussieht. Es hat einen Redux-Status von currentConversationId usw. Meine Messenger -Komponente hat @connect(state => ({ currentConversationId: state.messenger.currentConversationId })) .

Ich möchte diese Messenger in eine neue App aufnehmen. Ich muss import { rootReducer as messengerRootReducer } from 'my-messenger' und es zu combineReducers({ messenger: messengerRootReducer }) hinzufügen, damit es funktioniert.

Wenn ich nun zwei <Messenger> Instanzen in der App haben möchte, die unterschiedliche Daten haben, kann ich das nicht, da ich in der Messenger-Komponente an state.messenger gebunden bin. HerstellungWenn ich gegen einen bestimmten Teil des Geschäfts arbeite, verwende ich ein benutzerdefiniertes @connect .

Angenommen, die enthaltene App hat einen bestimmten Aktionsnamen, der im Modul my-messenger ist. Es kommt zu einer Kollision. Aus diesem Grund haben die meisten Redux-Plugins Präfixe wie @@router/UPDATE_LOCATION .

Ein paar zufällige / verrückte Ideen:

1

@connect kann eine Verbindung zu einem bestimmten Teil des Geschäfts herstellen, sodass ich in meine App mehrere <Messenger> s aufnehmen kann, die eine Verbindung zu ihrem eigenen Datenabschnitt herstellen, z. B. state.messenger[0] / state.messenger[1] . Es sollte für <Messenger> transparent sein, das weiterhin eine Verbindung zu state.messenger . Die enthaltende App kann das Slice jedoch wie folgt bereitstellen:

@connect(state => ({ messengers: state.messengers }))
class App extends Component {
  render() {
    return (
      <div>
        {this.props.messengers.map(messenger =>
          <ProvideSlice slice={{messenger: messenger}}><Messenger /></ProvideSlice>
        }
      </div>
    )
  }
}

Es gibt ein Problem bei der Verwendung global normalisierter Entitäten. state.entities muss ebenfalls vorhanden sein. Zusammen mit dem in Scheiben geschnittenen Status muss also der gesamte Status irgendwie vorhanden sein. ProvideSlice kann einen Kontext festlegen, aus dem Messenger @connect lesen kann.

Ein weiteres Problem ist das Binden von Aktionen, die von Komponenten in <Messenger> ausgelöst werden und nur dieses Slice betreffen. Vielleicht haben @connect unter ProvideSlice mapDispatchToProps Aktionen nur auf diesem Teil des Staates ausgeführt.

2

Kombinieren Sie die Definitionen store und reducer , sodass jeder Reduzierer auch eigenständig sein kann. Speichern klingt wie ein Controller, während ein Reduzierer eine Ansicht ist. Wenn wir den Ansatz von React "Alles ist eine Komponente" verfolgen (im Gegensatz zu Controller / Direktive von Angular 1.x), können Komponenten und deren Status / Aktionen möglicherweise wirklich eigenständig sein. Für Dinge wie normalisierte Entitäten (dh 'globaler Zustand') kann so etwas wie der Kontext von React verwendet werden, und es kann über alle Aktionen (z. B. getState in thunk ) / verbundene Komponenten zugegriffen werden.

@elado Die klügste Idee, die ich bisher gesehen habe, ist das Architektieren mit Blick auf "Anbieter": https://medium.com/@timbur/react -automatic-redux-provider-and-replicators-c4e35a39f1

Danke @sompylasar. Ich bin ein bisschen unterwegs, aber wenn ich die Gelegenheit dazu bekomme, plane ich, einen weiteren Artikel zu schreiben, in dem Anbieter für diejenigen, die bereits mit React und Redux vertraut sind, prägnanter beschrieben werden. Jeder, der bereits vertraut ist, sollte es einfach / leicht verrückt finden. :) :)

@gaearon würde immer noch gerne Ihre Gedanken dazu hören.

Mein größter Ärger mit Haustieren ist, dass es schwieriger ist, Containerkomponenten zu komponieren, wiederzuverwenden, zu verschachteln und sich im Allgemeinen zu bewegen, da zwei unabhängige Hierarchien gleichzeitig vorhanden sind (Ansichten und Reduzierungen). Es ist auch nicht ganz klar, wie wiederverwendbare Komponenten geschrieben werden sollen, die entweder Redux als Implementierungsdetail verwenden oder eine Redux-freundliche Schnittstelle bereitstellen möchten. (Es gibt verschiedene Ansätze.) Ich bin auch nicht beeindruckt davon, dass jede Aktion „ganz“ nach oben gehen muss, anstatt irgendwo kurzzuschließen. Mit anderen Worten, ich würde gerne etwas wie React local state model sehen, das jedoch von Reduzierern unterstützt wird, und ich möchte, dass es sehr praktisch ist und eher auf reale Anwendungsfälle als auf eine schöne Abstraktion abgestimmt ist.

@gaearon irgendwelche Ideen, wie man mit der Implementierung eines solchen Systems beginnen kann?

@gaearon

Mit anderen Worten, ich würde gerne so etwas wie das Modell des lokalen Zustands reagieren sehen, das jedoch durch Reduzierungen unterstützt wird

Ich denke, der schwierige Teil ist, dass der lokale Status von React an den Lebenszyklus einer Komponente gebunden ist. Wenn Sie also versuchen, den lokalen Status in Redux zu modellieren, müssen Sie "einhängen" und "aushängen". Elm hat dieses Problem nicht, weil 1) Komponenten keine Lebenszyklen haben und 2) das "Mounten" einer Ansicht vom Modell abgeleitet wird, während in React der lokale Status nur existiert, nachdem eine Ansicht gemountet wurde. (Bearbeiten: genauer gesagt direkt vor dem Mounten, aber nachdem die Entscheidung zum Mounten bereits getroffen wurde)

Aber wie Sie anspielen, hat die Elm-Architektur - die "ganz nach oben" geht - ihre eigenen Nachteile und passt nicht so gut zum Aktualisierungs- und Abstimmungszyklus von React (z. B. erfordert sie die liberale Verwendung von shouldComponentUpdate() -Optimierungen ... die brechen, wenn Sie eine Versandmethode innerhalb von render() naiv "weiterleiten".)

Ab einem bestimmten Punkt möchte ich nur noch Reduzierer + setState verwenden und es einen Tag nennen: D Es sei denn / bis React eine Geschichte für die Externalisierung des Statusbaums herausfindet ... obwohl wir das hier vielleicht wirklich diskutieren

Ich denke, das ist es, was @threepointone mit https://github.com/threepointone/redux-react-local zu lösen versucht

@acdlite Das konzeptionelle Modell von Elm scheint wirklich ein Problem zu sein, es in realen Anwendungen zu verwenden, in denen wir Bibliotheken verwenden, uns auf das Mounten konzentrieren, Parallaxeneffekte verarbeiten müssen oder was auch immer ... Siehe https: // github.com/evancz/elm-architecture-tutorial/issues/49

@slorber Ja, ich stimme zu. Ist das nicht im Wesentlichen das, was ich gerade gesagt habe? : D.

Ich nehme an, Ihre Bedenken sind etwas anders. _Wenn Sie (großes "Wenn") die Elm-Architektur in React verwenden würden, hätten Sie wahrscheinlich nicht die Probleme, die Sie erwähnen, da wir weiterhin Zugriff auf Lebenszyklen, setState als Notluke usw. hätten.

Ja, wir sind auf derselben Seite @acdlite
Die Ulmenarchitektur mit React würde dieses Problem lösen.
Sogar Elm könnte in Zukunft die Verwendung von Vdom-Hooks ermöglichen.

Die Elm-Architektur erfordert jedoch einige Boilerplate. Ich denke, es ist nicht wirklich nachhaltig, wenn Sie nicht Flow oder Typescript für das Ein- und Auspacken von Aktionen verwenden, und es wäre besser, eine einfachere Möglichkeit zu haben, mit diesem lokalen Status umzugehen, wie die Lösung von @threepointone

Ich habe ein weiteres Problem mit der Elm-Architektur: Die verschachtelte Aktion funktioniert hervorragend für den lokalen Komponentenstatus, aber ich denke, es ist keine so gute Idee, wenn Sie die Kommunikation zwischen entkoppelten Komponenten benötigen. Wenn Widget1 tief verschachtelte Aktionen auspacken und überprüfen muss, um ein von Widget2 ausgelöstes Ereignis zu finden, liegt ein Kopplungsproblem vor. Ich habe hier einige Gedanken geäußert und denke, dass die Verwendung von Elm mit 2 "Postfächern" den Job machen könnte. Dies ist auch das, wofür Redux-Saga in Nicht-Ulmen-Architekturen mit flachen Ereignissen helfen kann.

Ich habe eine gute Idee, wie man skalierbare Redux-Apps erstellt, und werde versuchen, etwas darüber zu schreiben, wenn ich weniger beschäftigt bin

@gaearon Sprechen Sie in Ihrem letzten Beitrag hier über react-redux-provide ? Ich frage, denn wenn Sie es sind, ist es aufgrund Ihrer Antwort klar, dass Sie es sich nicht genau genug angesehen haben.

In Bezug auf @acdlites Erwähnung der Externalisierung des Statusbaums ist dies genau das Problem, das Anbieter lösen sollen.

Die vom provide Dekorateur bereitgestellte API ist sicherlich eine Abstraktion und ja, sie ist derzeit von redux abhängig, aber es ist der falsche Weg, sie als abhängig von redux zu betrachten Denken Sie darüber nach, da es sich eigentlich nur um einen "Klebstoff" zwischen Komponenten und dem Statusbaum handelt, da Reacts context noch nicht vollständig entwickelt ist. Stellen Sie sich das als ideale Methode vor, um Statusbäume über actions und reducers (dh den Status des Geschäfts) als props manipulieren und darzustellen, sofern Sie dazu in der Lage sind im Wesentlichen die actions und reducers als propTypes (und / oder contextTypes in der Zukunft) zu deklarieren, ähnlich wie Sie import alles andere in Ihrer App. Wenn Sie dies tun, wird alles wahnsinnig leicht zu überlegen. Reacts context könnte sich möglicherweise weiterentwickeln, um diese grundlegenden Funktionen einzuschließen. In diesem Fall würde es ganze 2 Sekunden dauern, bis @provide und import provide from 'react-redux-provide' erfasst und entfernt werden, damit Sie es sind dann mit einfachen Komponenten verlassen, die nicht mehr davon abhängen. Und wenn das nie passiert, keine große Sache, denn alles würde weiterhin perfekt und vorhersehbar funktionieren.

Ich denke, Sie vereinfachen das Problem zu stark. Sie müssen die Montage und Demontage noch lösen.

@acdlite Hast du ein Beispiel?

Wenn eines der Dinge, auf die Sie sich beziehen, (zum Beispiel) das Abrufen von props aus einer Datenbank ist, basierend auf id oder was auch immer, dies (neben fast jeder anderen praktischen Anwendung, die Sie sich vorstellen können) kann leicht mit Anbietern erreicht werden. Nachdem ich einige andere Dinge erledigt habe, an denen ich arbeite, würde ich Ihnen gerne zeigen, wie einfach es ist. :) :)

Kann interessant sein: reaktive Zustandsimplementierungen im Vergleich .
Elm, Redux, CycleJS ... (in Arbeit)

Ein weiterer möglicher Bedarf für mehrere Speicher besteht darin, dass mehrere Zeitlinien, Zeitreisen, Speicherorte und Speicherhäufigkeit möglich und erforderlich sind. Zum Beispiel user data und user actions , die für einen einzelnen Benutzer spezifisch sind, und common data und group actions für eine Gruppe von Benutzern spezifisch sind (z. B. gemeinsam genutztes Mehrbenutzerprojekt oder Dokument). Dies erleichtert das Trennen von Rückgängigmachungen (Änderungen an personenbezogenen Daten) von Fixen (Änderungen, die bereits von anderen Benutzern vorgenommen wurden) und erleichtert das Speichern und / oder Synchronisieren unterschiedlicher Zyklen.

Wir befinden uns in der Entwurfs- / frühen Entwicklungsphase einer Angular 1.x-Anwendung mit einer Mindset 2.0-Entwicklungsmentalität und dem Gedanken, Redux zu verwenden. In der Vergangenheit haben wir Flussmittel mit mehreren Filialen verwendet, die auf einige allgemeine und bestimmte Sendungen reagieren.

Redux sieht für mich gut aus, aber ich kann das Single-Store-Konzept nicht für die gesamte Anwendung begründen. Wie bereits erwähnt, haben wir eine Reihe großer Widgets, die auf verschiedenen Verbraucheranwendungen gehostet werden sollen und für die jeweils dedizierte Datenspeicher erforderlich sind.

Obwohl Reduzierungen aufgeteilt werden können und mit der Funktion / dem Widget, die sie verwenden, leben können, müssen wir sie alle zusammenfassen, wenn es Zeit ist, den Store zu erstellen. Wir möchten, dass unsere Widgets existieren, ohne direkten / indirekten Verweis auf Reduzierungen zu haben, die ihm egal sind.

Wir freuen uns sehr auf eine Anleitung in Form einer Dokumentation oder eines Kommentars, um die Best Practice in solchen Situationen zu skizzieren, bevor Sie Redux in unserer App verwenden. Jede Hilfe wird sehr geschätzt. Vielen Dank.

@VivekPMenon : Was sind Ihre spezifischen Anliegen? Größe des Staatsbaums? In der Lage sein, Aktionen zu benennen / Reduzierungen isoliert zu halten? Fähigkeit, Reduzierstücke dynamisch hinzuzufügen oder zu entfernen?

Für das, was es wert ist, habe ich gerade eine Linkliste ausgefüllt, die das Redux-Addon-Ökosystem katalogisiert. Es gibt eine Reihe von Bibliotheken, die versuchen, einige dieser Isolations- / Duplizierungs- / Scoping-Konzepte anzugehen. Vielleicht möchten Sie einen Blick auf die Reduziererseite und die lokale / Komponentenstatus-Seite werfen, um zu sehen, ob eine dieser Bibliotheken bei Ihren Anwendungsfällen hilfreich sein kann.

@markerikson. Vielen Dank für die Antwort. Meine Verwirrung dreht sich hauptsächlich um den 2. Punkt (Isolation). Angenommen, eines unserer Teams erstellt ein Widget A, das auf Flussmitteln basiert und für die Verwaltung seines Status den Speicher verwenden muss. Angenommen, sie erstellen ein jspm / npm-Paket aus if und verteilen es. Gleiches gilt für ein anderes Team, das Widget B erstellt. Dies sind unabhängige Widgets / Pakete, die keine direkte / indirekte Abhängigkeit voneinander haben müssen.

In diesem Zusammenhang in der regulären Flusswelt. Widget A und B haben ihre eigenen Speicher in ihrem Paket (sie hören möglicherweise einige allgemeine Aktionen ab, gehen jedoch davon aus, dass sie mehr Aktionen haben, die nicht häufig sind). Beide Widgets sind vom globalen Dispatcher abhängig. Wo würde dieser Speicher im Redux-Muster (physisch) erstellt? Sowohl die UI-Komponente von Widget A als auch von B muss die Speicherinstanz verwenden, um Änderungen abzuhören und den Status abzurufen. Im regulären Fluss arbeiten sie auf ihrer eigenen Geschäftsinstanz. Ich versuche zu visualisieren, wie das Single-Store-Konzept mit dieser Situation umgehen wird.

@VivekPMenon Dies ist eine gültige Frage, die einige von uns zu lösen versuchen.

Hier erhalten Sie einige Einblicke: https://github.com/slorber/scalable-frontend-with-elm-or-redux

Schauen Sie sich auch meine Antwort auf die Redux-Saga hier an: http://stackoverflow.com/a/34623840/82609

@slorber Vielen Dank für die Anleitung. Ich freue mich auf die Ideen, die hier auftauchen: https://github.com/slorber/scalable-frontend-with-elm-or-redux

@taggartbg Hast du irgendwann eine Ausgabe geöffnet? Ich interessiere mich für eine ähnliche Sache - wenn ich einen React-Router mit verschiedenen unabhängigen Unterbäumen habe, finde ich es problematisch, den Status der vorherigen Route beizubehalten, während ich zu einem neuen Zweig navigiere.

@slorber

Lassen Sie mich zunächst sagen, dass die Redux-Saga ein wunderbares Projekt ist, in das sehr viel investiert wurde, und dies versucht keineswegs, dies zu verringern.

Aber ich würde es in keinem meiner Projekte im aktuellen Zustand verwenden. Hier sind einige Gründe warum

Sagen sind staatenlos

Sie können den Status eines Generators nicht serialisieren. Sie sind während des Betriebs sinnvoll, aber vollständig an den Status der Saga gebunden. Dies führt zu einer Reihe von Einschränkungen:

  • Sie können keine Zeitreisegeneratoren
  • Sie können den Pfad, der zum aktuellen Status einer Saga führte, nicht verfolgen
  • Sie können keinen Ausgangszustand definieren
  • Sie können die Geschäftslogik nicht vom Anwendungsstatus [*] trennen.
  • Sie können keine Sagen speichern

[*] Dies könnte so interpretiert werden, dass die Geschäftslogik innerhalb der Definition des Anwendungsmodells definiert wird, sodass M und C in MVC dasselbe sind, was als Antimuster betrachtet werden könnte

Das Versenden von Aktionen ist nicht deterministisch

Einer der großen Vorteile der Verwendung von Redux besteht darin, ein einfaches Verständnis dafür zu haben, wie der Staat auf eine Aktion reagiert. Aber Redux-Saga macht Aktionen anfällig für unerwartete Ergebnisse, da Sie nicht sagen können, in welchem ​​Teil der Saga Sie beginnen.

Serialisierbarer Zustand

Die Sache ist, dass der aktuelle Status der Saga einen Einfluss auf den aktuellen Status der Anwendung hat und die Leute sich darum bemühen, den Status serialisierbar zu halten, aber wie übersetzen Sie das in Sagen? Wenn Sie den Status nicht vollständig serialisieren können, serialisieren Sie ihn nicht wirklich. Grundsätzlich können Sie den Status einer Anwendung nicht wiederherstellen, ohne auch die Saga wiederherzustellen.


Das Speichern von Logik im Zustand ist schlecht, und meiner Meinung nach ist dies das, was die Redux-Saga in gewisser Weise tut.

Es ist vollkommen in Ordnung anzunehmen, dass der Status Ihrer Anwendung für Ihre Anwendung spezifisch ist. Das bedeutet, dass es auch in Ordnung ist anzunehmen, dass der Status der Anwendung vorhersehbar ist, solange sie über Ihrer Anwendung ausgeführt wird. Dies bedeutet, dass es in Ordnung ist, Informationen über den Speicher beizubehalten, die bestimmen, wie sich Ihre Anwendung verhält, ohne die Logik dort tatsächlich zu speichern.

@eloytoro

Ich verstehe deine Bedenken. Wenn Sie sich Redux-Saga-Probleme ansehen, werden Sie feststellen, dass diese Dinge diskutiert werden und es Lösungen gibt.

Beachten Sie auch, dass sich die Redux-Saga für die Implementierung mit Generatoren entscheidet, die Implementierung des Saga-Musters jedoch überhaupt nicht erforderlich ist, und es gibt Vor- und Nachteile.

Es gibt einen Auswahleffekt, mit dem Sie den Redux-Status in Ihrer Redux-Saga verwenden können. Wenn Sie den Status also nicht in Generatoren verbergen möchten, können Sie Ihren Saga-Status einfach direkt im Redux-Store ablegen, dies erfordert jedoch mehr Arbeit.

Es gibt Saga Devtools, mit denen die Ausführung von Saga verfolgt werden kann.

Es gibt mögliche Lösungen für Zeitreisen, aber noch keine konkrete Implementierung

Recht. Ein ziemlich langes Thema und sehr interessant. Ich habe verschiedene Ansätze des Staatsmanagements verglichen.

Beim Vergleich denke ich an einige Dinge:

  1. einzige Quelle der Wahrheit
  2. wie einfach / schwierig es ist, serverseitig zu rendern und anfängliche Daten clientseitig einzugeben
  3. Devtools wie Rückgängig / Wiederherstellen und dauerhafte Verlaufsspeicherung nach dem Neuladen.
  4. Animationen, Animationen, Animationen. Ich frage mich immer, wie Animationen mit Lösung a oder b funktionieren würden. Dies ist ein Schlüssel für mich, da die Produkte, die ich entwickle, UX-zentriert sind. Ich teile viele der Verwirrungen, die @jsonnull hat.
  5. Wiederverwendung von Komponenten in einer anderen App
  6. Hydratisierung der referenzierten Daten (_major pain_), um die Integrität aufrechtzuerhalten (im Zusammenhang mit der Eingabe von Anfangsdaten von der Serverseite oder einer anderen Datenquelle)
  7. Gemeinschaft anderer Entwickler zur Diskussion wie diese

Das sind also Punkte, vor denen ich vorsichtig bin, wenn ich über Staatsmanagement nachdenke. Redux ist der Gewinner, indem die meisten Kästchen angekreuzt werden. Ich kenne Redux ziemlich gut und es waren die ersten Dinge, die mich dazu gebracht haben, im Bereich der clientseitigen Js ruhig zu atmen.

Die bemerkenswertesten anderen Projekte, die ich untersucht habe, sind das SAM-Muster (schwer in die Denkweise zu kommen und keine Werkzeuge) und Mobx (bekannt für seinen veränderlichen Speicher).

Beide teilen die Idee einer einzigen Quelle der Wahrheit und ich finde es interessant zu sehen, wie sie damit umgehen.

Meine (natürlich begrenzte) Forschung über Mobx führt dazu:

  1. Mobx (genau wie Redux) plädiert für eine einzige Quelle der Wahrheit, aber als Klasse mit beobachtbaren Eigenschaften. Dieses Konzept schafft die Unterschiede mit Redux und Fluss als Ganzes:

    1. Mobx befürwortet auch Sub-Stores (_nested / Teil des globalen Single One_). Für mich sieht es sehr danach aus, ein clientseitiges Schema zu definieren. Es ist ein bisschen nervig, da Sie wahrscheinlich auch ein Schema auf dem Server haben. Ich sehe Ähnlichkeiten mit redux-orm . AFAIK Die Sub-Stores sollen als Requisiten an Komponenten übergeben werden (auf diese Weise ist die Komponente nicht auf die Zuordnung des Status zu Requisiten oder bestimmten Schlüsseln des globalen Baums angewiesen). Das Übergeben einer einzelnen Eigenschaft bricht jedoch meine Vorstellung von "Eigenschaften" - sie sollten beschreiben, was die Komponente benötigt. Dies lässt mich tatsächlich überlegen, ob dies ein Problem wäre, wenn React propTypes verwendet werden kann, um die Form des übergebenen Geschäfts zu definieren. Außerdem: Wenn Sie Store- und Sub-Stores als Klassen haben, können Sie die Daten ohne Normalisierung direkt referenzieren. Dies ist jedoch sinnlos, wenn Sie Anfangsdaten eingeben möchten, weil:

    2. Die Idee, als Klasse zu speichern, bedeutet jedoch, dass der Entwickler keine sofort einsatzbereite Serialisierung hat. Dies bedeutet, dass Sie dies tun müssen, wenn Sie anfängliche Daten eingeben möchten.

  2. Verlauf und Rückgängigmachen / Wiederherstellen können durch Erstellen von Schnappschüssen des Status nach dessen Änderung erreicht werden. Aber Sie müssen Serializer schreiben, Deserializer selbst. Ein Beispiel finden Sie hier: https://github.com/mobxjs/mobx-reactive2015-demo/blob/master/src/stores/time.js
  3. Das Gute am Schreiben von Serialisierern ist, dass Sie Dinge weglassen können. Es scheint ein guter Ort zu sein, um nicht persistente animationsbezogene Daten zu speichern. Wenn Sie es also serialisieren, ignorieren Sie es einfach und Sie haben kein Wiederherstellen / Rückgängigmachen voller Animationsschritte.
  4. Mobx plädiert dafür, Ableitungen von Daten im Geschäft (als Methoden des Geschäfts) beizubehalten, im Gegensatz zu reselect + connect Dekorateur. Das scheint mir irgendwie falsch zu sein.
  5. Mobx macht es aufgrund seiner beobachtbaren Natur einfach, effiziente reine Komponenten zu definieren. Ein flacher Vergleich der Eigenschaften ist kein Problem, da der Inhalt ohnehin beobachtbar ist.

Beim Vergleich von SAM und Redux denke ich gerne darüber nach, wie die Logik fließt:

  1. Redux:

    1. Definieren Sie ein Geschäft

    2. Definieren Sie Funktionen, die Speicheränderungen behandeln (Reduzierungen).

    3. Definieren Sie Ereigniskennungen (Redux-Aktion) (Redux-Aktionstyp) für jede Geschäftsänderung und führen Sie einen bestimmten Reduzierer aus, wenn dieser übereinstimmt

    4. Ordnen Sie den Speicher den Komponentencontainereigenschaften zu und übergeben Sie optional Handler der Benutzerinteraktion (Klicken, Typ, SScroll usw.) als Funktionen, die einfach eine Ereignis- / Dispatch-Aktion auslösen.

    5. Die Komponente löst ein Ereignis (Redux-Aktion) aus, das mit allen Reduzierern abgeglichen wird

    6. Rendern Sie mit dem neuen Zustand.

  2. SAM:

    1. Definieren Sie ein Geschäft.

    2. Definieren Sie eine Accessor / Setter-Funktion für das Geschäft. Dieser Setter akzeptiert alle Daten und basiert ausschließlich auf den Daten, die er herausfinden möchte, wo sie im Speicher abgelegt werden sollen und ob er Daten vom Server anfordern oder dort beibehalten muss. Es ist nicht immer möglich, herauszufinden, was mit den Daten zu tun ist, indem man sie nur überprüft. Daher werden manchmal Bezeichner / Flags verwendet (wie isForgottenPassword ). Dieser Setter soll auch die Daten validieren und stattdessen die Fehler speichern. Nachdem der Setter fertig ist -> mit dem neuen Status erneut rendern.

    3. Definieren Sie Funktionen, die Daten akzeptieren, transformieren Sie diese (Alternative zu Redux-Reduzierern) und führen Sie den Setter aus, der die transformierten Daten übergibt. Diese Funktionen haben nun eine Vorstellung von der Form des Geschäfts. Sie kennen nur die Daten, die ihnen übergeben wurden.

    4. Ordnen Sie Speicherdaten den Komponentencontainereigenschaften zu, aber es ist auch obligatorisch, Handler von Benutzerinteraktionshandlern zu übergeben (Klicken, Typ, Scrollen usw.). Beachten Sie, dass dies die eigentlichen Funktionen sind (sogenannte Redux in Redux) und keine Funktionen, die einen Versandaufruf ausführen.

    5. Hinweis: Es erfolgt kein Versand von Aktionen, kein direktes Übergeben von Reduzierstücken.

Alle Redux, SAM und Mobx halten den Status an einem Ort. Die Art und Weise, wie sie es tun, bringt ihre eigenen Rückschläge mit sich. Bisher kann niemand Redux und sein Ökosystem schlagen.

Der größte Nachteil, den ich sehe, ist auch der Umgang mit nicht serialisierbaren Zuständen.
In meinem Fall habe ich eine Plugin-API und daher Aktionen und Logik, die nichts mit Redux zu tun haben, sondern mit meiner Anwendung interagieren müssen. Im Grunde muss ich zunächst eine Reihe von Klassen basierend auf einem bestimmten Status neu erstellen.

Ich arbeite an einigen Konzepten, wie man mit dieser Art von nicht serialisierbarem Zustand besser umgehen kann. Einschließlich der Neuerstellung aus einem bestimmten Zustand. Ich habe es bereits geschafft, alles außer Funktions- / Objektreferenzen zurück in den Laden zu verschieben. Was bleibt, sind Objekte mit ihren eigenen Lebenszyklen mit einer definierten API, die mit meiner Anwendung und damit mit meinen Aktionen interagieren. - Fühlt sich ähnlich an wie React-Komponenten, nur nicht für das Rendern der Benutzeroberfläche, sondern für benutzerdefinierte Logik.

Ich weiß nicht, ob ihr diesen Artikel von Erik Meijer gelesen habt. Nur ein einfaches Zitat:

Entgegen der landläufigen Meinung kommt die Unveränderlichkeit von Zustandsvariablen der Beseitigung inakzeptabler impliziter imperativer Effekte bei weitem nicht nahe

Vielleicht ist es an der Zeit, diesen ganzen Unsinn der Unveränderlichkeit noch einmal zu überdenken. Jeder wird Ihnen sagen, dass dies ein Leistungsproblem ist, wenn der einzelne Statusbaum größer wird. Das SAM-Muster macht die Mutation zu einem Bürger der Primärklasse des Programmiermodells. Viele Vereinfachungen treten auf, wenn Sie Sagas nicht mehr benötigen, bis hin zu all diesen zusätzlichen Zustandsmaschinen, die erforderlich sind, um die Auswirkungen zu verfolgen (z. B. Abrufen).

Der Artikel spricht nicht wirklich für Sie. Erik Meijer greift "diesen Unveränderlichkeitsquatsch" noch einmal auf und nimmt ihn vollständig an . Er argumentiert, dass das Entfernen von Zustandsmutationen ein grundlegender erster Schritt ist , grundlegend, aber nicht ausreichend. Sein letztendliches Ziel ist die vollständige Beseitigung impliziter Nebenwirkungen, die eine staatliche Verwaltung überflüssig machen würden (es gäbe keinen zu verwaltenden Staat!).

Ich bin ein Fan von Erik Meijers Arbeit und werde alles, was er zu sagen hat, eifrig lesen und anhören, wobei ich auch bedenke, dass er nicht unter den gleichen geschäftlichen, technologischen und intellektuellen Bedingungen arbeitet wie ich.

Das Hauptproblem in der Softwareentwicklung ist, dass jeder zu glauben scheint, dass eine Zuordnung eine IS-A-Mutation ist. Programmiersprachen unterstützen nur Zuweisungen, die Mutation bleibt der Interpretation des Programmierers überlassen.

Das Hinzufügen eines Funktions-Wrappers zu einer Reihe von Zuweisungen ist nicht automatisch der beste Weg, um den Status zu ändern. Das ist Unsinn.

Es gibt eine Theorie hinter (Anwendungs-) Zustandsmutation, sie heißt seltsamerweise TLA + ... Erik erwähnt TLA + nie ...

JJ-

Lassen Sie mich näher darauf eingehen, da es viel Verwirrung zu geben scheint. Eriks Artikel zeigt eloquent, dass imperative Programmiersprachen in Bezug auf den Mutationszustand etwas kaputt sind. Ich glaube, da sind sich alle einig.

Die Frage ist, wie gehen Sie vor und beheben es? Würden Sie nicht zustimmen, dass dies ein Problem ist, das einen Ansatz des " ersten Prinzips " erfordert?

Es gibt so ziemlich ein einziges Prinzip, auf das wir uns in der Informatik alle einigen können, sagt Dr. Lamport: "Ein Großteil der Informatik handelt von Zustandsmaschinen." und er fährt fort: "Dennoch konzentrieren sich Informatiker so sehr auf die Sprachen, die zur Beschreibung der Berechnung verwendet werden, dass sie weitgehend nicht wissen, dass diese Sprachen alle Zustandsmaschinen beschreiben."

In der Tat ermöglichen es uns alle Programmiersprachen, "Zustandsmaschinen" unabhängig von ihrer Semantik anzugeben. Leider sind viele Programmiersprachen eher auf "Aktion" als auf "staatliche Aktion" ausgerichtet. Das ist aus gutem Grund so, dass es eine große Klasse von Problemen gibt, die mit einem handlungsbasierten Formalismus (bei dem die Zustände der Zustandsmaschine weitgehend ignoriert werden können) viel effizienter codiert werden. Wenn Sie sie ignorieren, geben Sie im Wesentlichen an, dass alle Aktionen in einem bestimmten Zustand möglich sind, von dem wir alle wissen, dass er im Allgemeinen nicht wahr ist.

Sie können über Eriks haha-Beispiel so viel lachen, wie Sie möchten:
// prints Ha var ha = Ha(); var haha = ha+ha;

Aber alles, was er sagt, ist, dass Sie in einem bestimmten Zustand die falschen Maßnahmen ergreifen. Nicht mehr, nicht weniger.

Was bringt funktionale Programmierung auf den Tisch? überhaupt nicht sehr viel. Es drückt auf sehr verschlungene Weise die Beziehung zwischen dem Ergreifen einer Maßnahme und den möglichen Ergebnissen aus. Es ist ein bisschen so, als würde man versuchen, einen Pfeil (sogar einen intelligenten monadischen Pfeil) in ein riesiges Labyrinth zu werfen und zu hoffen, dass Sie Ihr Ziel treffen.

TLA + bietet eine viel natürlichere Möglichkeit, mit Effekten umzugehen, zum einen, weil es eine klare Vorstellung davon hat, was ein Schritt ist und wie sich Mutation auf Aktion auswirkt. Es steht Ihnen frei, TLA + zu ignorieren, aber es ist ein bisschen so, als würden Sie sich beschweren, dass Sie versucht haben, eine Rakete zum Mond zu schicken, unter der Annahme, dass die Erde flach ist und es wirklich schwierig ist, das Raumschiff zu steuern. Ich weiß, dass wir in einer Welt leben, in der die Menschen glauben, dass sie die Realität biegen können, solange genug Menschen glauben, was sie sagen ... dass das Sie immer noch nirgendwohin bringt.

Also noch einmal, ich wäre wirklich sehr, sehr, sehr neugierig zu sehen, was Erik über TLA + denkt. Ich habe mich mit ihm auf LinkedIn verbunden und die Frage gestellt. Ich habe nie eine Antwort bekommen ... Immerhin hat Dr. Lamport nur einen Turing-Preis dafür bekommen, es ist wahrscheinlich seine Zeit nicht wert.

Ehrlich gesagt, @jdubray , Sie sind an diesem Punkt Borderline-Trolling. Sie haben Ihre Argumente Dutzende Male in diesem Thread, anderen Themen und anderswo wiederholt. Sie haben Dan und die Redux-Community beleidigt, Ihre Anmeldeinformationen herumgeschwenkt und die Wörter "TLA +" und "Lamport" immer wieder aufgerufen, als wären sie der Heilige Gral und die Antwort auf das Leben, das Universum und alles. Sie haben darauf bestanden, dass SAM Redux weit überlegen ist, ohne jedoch zahlreiche aussagekräftige Vergleichs-Apps zu schreiben, trotz zahlreicher Aufforderungen, Beweise vorzulegen. Sie werden wirklich niemanden umstimmen, der noch nicht überzeugt ist. Ich schlage ehrlich vor, dass Sie Ihre Zeit damit verbringen, etwas Produktiveres zu tun.

Ich werde den Thread an dieser Stelle schließen. Kommentare können fortgesetzt werden, denke ich, aber hier ist definitiv nichts umsetzbar.

@markerikson Ich möchte diese Diskussion fortsetzen, auch wenn sie nirgendwohin führt. Das Thema interessiert mich sehr. Es wäre besser, seine giftigen Kommentare auszuräumen und die gute Open-Source-Diskussion am Laufen zu halten.
Abgesehen davon, dass @antitoxic (kein Wortspiel beabsichtigt) deutlich gemacht hat, wo der Ansatz, auf den er sich bezieht, nützlich ist oder nicht, wissen wir, wie schlimm die anderen Probleme aufgrund seiner Interventionen geworden sind, sodass es keinen Sinn mehr macht, zuzuhören

Es ist in Ordnung, Mark, du kannst meine Kommentare entfernen. Warum so ein brillantes Gespräch verderben?

@jdubray nur du siehst es als brillant an. Ich denke, wir alle haben das Gefühl, dass Sie versuchen, uns etwas zu verkaufen, ohne mehr Argumente als "Lamport hat einen Turing-Preis erhalten, also hören Sie mir zu, ich habe Recht!".

Ich sage nicht TLA + oder was auch immer nicht gut ist, und vielleicht wird in Zukunft jemand einen besseren Job machen als Sie, der es verkauft. Es ist nur die Art und Weise, wie Sie es erklären, die uns überhaupt nicht hilft, es zu verstehen, und Ihr ständiger herablassender Ton lädt mich nicht ein, Zeit zu investieren, um etwas über TLA + zu lernen.

Eriks Artikel zeigt eloquent, dass imperative Programmiersprachen in Bezug auf den Mutationszustand etwas kaputt sind. Ich glaube, da sind sich alle einig.

Tatsächlich verwenden Sie in all Ihren Posts immer Begriffe wie I believe everyone agrees on that . Dies ist nichts Gutes, Hilfreiches oder Willkommenes für Menschen. Wir sind standardmäßig nicht alle mit Ihnen einverstanden, und die meisten von uns haben Eriks Zeitung nicht gelesen und werden sie auch nicht lesen. Wenn Sie Menschen nicht auf einladende Weise überzeugen können, ohne sie vorher zu bitten, Tonnen von Papier zu lesen, und sie sich dumm fühlen lassen, wenn sie nicht mit Ihnen übereinstimmen oder nicht, hilft dies nicht, Ihre Ideen zu verbreiten.

Es steht Ihnen frei, TLA + zu ignorieren, aber es ist ein bisschen so, als würden Sie sich beschweren, dass Sie versucht haben, eine Rakete zum Mond zu schicken, unter der Annahme, dass die Erde flach ist und es wirklich schwierig ist, das Raumschiff zu steuern.

Dies sieht definitiv wie eine Analogie aus und sicherlich nicht wie ein erstes Prinzip . Glauben Sie ernsthaft, dass diese Aussage als "brillante Diskussion" anzusehen ist?

Ich denke, die Diskussionen sind tatsächlich brillanter, wenn Sie nicht in ihnen sprechen, weil Sie die gesamte Aufmerksamkeit auf TLA + monopolisieren und tatsächlich verhindern, dass Leute, die nicht an TLA + interessiert sind, versuchen, ihre Rakete mit etwas Konkreterem als dem Durchschnitt zum Mond zu schicken Leute können verstehen, wie Redux.

Immer noch nicht zu sagen, dass TLA + es nicht wert ist, nur dass Sie falsch darüber sprechen, mit den falschen Leuten. Vielleicht, wenn Erik dir nicht geantwortet hat, weil er dich auch nicht verstehen kann? Vielleicht bist du so schlau, dass nur Lamport dich verstehen kann? Vielleicht gewinnen Sie in Zukunft einen Turing-Preis? Ich weiß es nicht, aber eines ist sicher, dass die aktuelle Diskussion nichts auf den Tisch bringt.

Vielleicht magst du

@Niondir Ich habe mir eine Möglichkeit
Es ist eher ein Muster oder eine Reihe von Regeln, denen man folgen muss

  • Trennen Sie Ihre Sagen so, dass jeder von ihnen nicht mehr als ein einzelnes put . Wenn Sie Sagen mit mehr als put haben, teilen Sie diese auf und verketten Sie sie mit einem call -Effekt. Z.B
function* mySaga() {
  yield put(action1());
  yield put(action2());
}

// split into

function* mySaga() {
  yield put(action1());
  yield call(myNextSaga);
}

function* myNextSaga() {
  yield put(action2());
}
  • Sagas mit take -Effekten sollten in die Logik aufgeteilt werden, die auf take und den Startpunkt der Saga folgt. Z.B
function* mySaga() {
  yield take(ACTION);
  // logic
}

// split it into

function* rootSaga() {
  yield takeLatest(ACTION, mySaga);
}

function* mySaga() {
  // logic
}
  • Erstellen Sie für jede Saga eine Art "Checkpoint" -Funktion, was bedeutet, dass Sie bei init aus dem Status und von dort aus call die Saga lesen, die Sie benötigen
function* rootSaga() {
  // emit all forks and take effects that need to run in the background
  yield call(recoverCheckpoint);
}

function* recoverCheckpoint() {
  const state = yield select();
  if (state.isFetching) {
    // run the saga that left the state like this
  }
}

Die Erklärung, warum dies funktionieren würde, basiert auf einer Reihe von Annahmen, die im Allgemeinen zutreffen

  • take -Effekte müssen Aktionen an den Store gesendet werden. Es ist sicher, diese Sagen immer bei init aufzurufen, auch wenn Sie sie wiederherstellen, da sie inaktiv bleiben, bis der Benutzer mit der Seite interagiert und / oder die Sagen bereits ausgeführt werden
  • Wenn nicht mehr als ein put -Aufruf pro Saga zugelassen wird, können atomare Änderungen im Status vorgenommen werden. Ebenso wie eine Aktion den Status nur auf eine einzige Weise ändern kann, ist eine Saga auch auf diese Kapazität beschränkt, die durch diese Art erstellt wird eine Korrelation zwischen Sagen und Action Dispatching.

Das Aufteilen von Sagen auf diese Weise hat auch einige positive Effekte.

  • Sagen sind wiederverwendbarer
  • Sagen können leichter geändert werden
  • Sagen sind leichter zu verstehen

aber @slorber :

Viele Leute finden dieses "Angelspiel" -Beispiel sehr interessant für den Code, da es aus erster Hand zeigt, wie SAM in seiner Gesamtheit funktioniert, einschließlich einer dynamischen Zustandsdarstellung und des "Next-Action-Prädikats". NAP verringert die Notwendigkeit von Sagas und erfordert nicht das Brechen des Redux-Prinzips Nr. 1, das ein einzelner Zustandsbaum ist, aber was weiß ich?

Wenn ich Ableton Live verwende, taucht manchmal der Gedanke in meinem Kopf auf: "Wäre es möglich, die Ableton Live-Benutzeroberfläche als React / Redux-App zu schreiben?" Ich denke, die Antwort ist nein, es wäre einfach zu schwierig, eine akzeptable Leistung zu erzielen. Es gibt einen Grund, warum es in Qt geschrieben ist, und einen Grund, warum Qt eine Signal- / Slot-Architektur hat.

Ich habe React / Redux mit Meteor verwendet. Ich speichere Mongo-Dokumente im Status Immutable.js, indem ich Redux-Aktionen für die Rückrufe an Mongo.Collection.observeChanges . Ich habe kürzlich festgestellt, dass die Benutzeroberfläche beim Abonnieren von mehreren tausend Dokumenten beim Start extrem verzögert war, da Tausende von Redux-Aktionen ausgelöst wurden, als Meteor die ersten Abonnementergebnisse nacheinander sendete, was Tausende von Immutable.js-Vorgängen und Tausende von Renderern so schnell verursachte wie möglich. Ich gehe davon aus, dass RethinkDB auch so funktioniert, obwohl ich mir nicht sicher bin.

Meine Lösung bestand darin, eine spezielle Middleware zu erstellen, die diese Erfassungsaktionen beiseite legt und sie dann mit einer gedrosselten Rate stapelweise versendet, sodass ich in einer einzigen Statusänderung ~ 800 Dokumente hinzufügen kann. Die Leistungsprobleme wurden behoben, aber das Ändern der Ereignisreihenfolge ist von Natur aus riskant, da dies zu einem inkonsistenten Status führen kann. Zum Beispiel musste ich sicherstellen, dass die Aktionen für den Abonnementstatus über denselben Throttler ausgelöst wurden, damit die Abonnements nicht als bereit markiert wurden, bevor alle Dokumente zum Redux-Status hinzugefügt wurden.

@ jedwards1211 Ich hatte ein ähnliches Problem (viele kleine Nachrichten, die die ursprünglichen Daten zusammensetzen würden).

Auf die gleiche Weise durch ein Batch-Update gelöst. Anstelle einer Middleware habe ich einen (kleinen) benutzerdefinierten Warteschlangenmechanismus verwendet (alle Updates in einem Array pushen und in regelmäßigen Abständen, wenn etwas aktualisiert werden muss, alle gleichzeitig aktualisieren.

@andreieftimie Ja, es ist schwieriger in meiner App, da es einige UI-Interaktionen gibt, die sofortige Aktualisierungen erfordern, wie z. B. Ziehen und Zoomen von Plots. Ich muss also eine Middleware-Schicht haben, um zu entscheiden, ob eine Aktion sofort oder in eine Warteschlange für den Batch-Versand gestellt werden soll.

Für das, was es wert ist, hatte ich die Idee, "Zweige" zu schaffen. Es widerspricht völlig dem Single-Store-Prinzip, aber es schien ein guter Kompromiss zu sein.

https://github.com/stephenbunch/redux-branch

@ jedwards1211 Ich denke, dass Apps mit zunehmender Komplexität letztendlich die Notwendigkeit haben, bestimmte Datentypen und bestimmte Arten von Updates bewältigen können.

Angenommen, es gibt einen Kernstatus, den Sie mit dem Backend synchronisieren möchten, einen Animationsstatus, damit die Komponenten Ihrer App-Benutzeroberfläche entsprechend den aktuell ausgeführten Aktionen korrekt angezeigt werden, und möglicherweise haben Sie einige Debug-Daten, die Sie verfolgen separat.

In diesem erfundenen Beispiel ist es sicherlich möglich, all dies aus einem einzigen Redux-Geschäft zu erstellen, aber je nachdem, wie Sie es entwerfen, gibt es einige verschwommene Linien zwischen den Bedenken verschiedener Zweige des Statusbaums und möglicherweise Unklarheiten in Bezug auf welchen Zustand eine bestimmte Aktion interagieren soll.

Ich denke, es ist durchaus vernünftig, unterschiedliche Strategien für das Statusmanagement zu verwenden, je nachdem, wie der Status des Status sein soll und welche Leistungsmerkmale Sie von ihm erwarten. Redux ist in geeigneter Weise auf den Zustand ausgerichtet, in dem Sie möglicherweise Aktionen zurücksetzen oder wiedergeben oder serialisieren und zu einem späteren Zeitpunkt zurückkehren möchten. Für den Animationsstatus können Sie einen veränderlichen Speicher oder einen reaktiven Speicher verwenden, wenn Sie möchten. Auf diese Weise entsteht weniger Overhead, und Sie verschmutzen Ihren Anwendungsstatus nicht mit diesem vorübergehenden (aber immer noch erforderlichen) Interaktionsstatus.

Ganz schnell möchte ich einen Punkt mit der kommenden React-Faserarchitektur machen. Hier ist ein Link , entschuldige mich, wenn mein Verständnis etwas veraltet ist. Die Glasfaserarchitektur erkennt ungefähr an, dass es verschiedene Arten von Aktualisierungen gibt, die sich über einen React-Komponentenbaum verbreiten, und es gibt unterschiedliche Erwartungen hinsichtlich des Zeitpunkts und der Leistung dieser Aktualisierungen. Sie möchten, dass Animationsupdates schnell, reaktionsschnell und zuverlässig sind, und Sie möchten nicht, dass große Interaktionsupdates Ihren Animationen und dergleichen einen Ruck verleihen.

Die Glasfaserarchitektur zerlegt Aktualisierungen in Arbeitspakete und plant sie gemäß einer Priorität, die auf der ausgeführten Arbeit basiert. Animationen haben eine hohe Priorität, damit sie nicht unterbrochen werden und langsamere mechanische Aktualisierungen eine niedrigere Priorität haben.

Ich habe viele Details zu React-Fasern übersprungen und dabei wahrscheinlich einige Fehler gemacht, aber mein Punkt ist, dass dies der granulare Ansatz ist, den ich für Ihren Datenspeicher mit verschiedenen Datentypen für notwendig halte.

Wenn ich heute eine komplexe App erstellen würde, würde ich mit einer Redux-ähnlichen Top-Level-Store-Klasse beginnen, die von einigen verschiedenen Stores unterstützt wird. Grob:

  • Der Datenspeicher der obersten Ebene verfügt über eine Warteschlange für eingehende Aktionen
  • Aktionen sind entweder für Animationsaktionen gedacht, die schnell ausgelöst werden sollen, oder für Anwendungsinteraktionen mit niedrigerer Priorität
  • Animationsaktionen in der Warteschlange werden von einer requestAnimationFrame-Schleife an ein beobachtbares Backend gesendet
  • Interaktionsaktionen werden an Redux gesendet, wenn die Animationsaktionen aus der Warteschlange abgeschlossen sind

Diese Geschichte scheint einer vollständigen, auf Redux basierenden staatlichen Lösung ziemlich nahe zu kommen. Redux eignet sich für Daten, die Sie nach einer Abfolge von Aktionen anzeigen möchten, und es gibt viele andere Status, die ebenfalls sorgfältig behandelt werden müssen.

@jsonnull Ich hatte noch nie die Notwendigkeit, den Animationsstatus in einem Geschäft zu speichern. Der lokale Status war immer ideal für meine Anwendungsfälle für Animationen. Ich wäre gespannt, ob es einige Anwendungsfälle gibt, für die der lokale Animationsstatus völlig ungeeignet ist. Es klingt jedoch so, als hätten sie großartige Ideen für die neue Architektur.

@ jedwards1211 Es gibt einige Fälle, in denen ich mir

In einem Fall verwenden Sie eine andere Bibliothek als Redux, in der Sie keinen lokalen Status haben. (Ok, ja, ich weiß, dass ich hier ein wenig betrüge.) Wenn Sie einen sehr schlanken Hyperscript-Ansatz verwenden, keinen virtuellen Dom, reine Funktionskomponenten, müssen Sie anstelle des lokalen Status eine Animation übergeben Geben Sie den Stammknoten oder den Knoten an, den Sie neu rendern.

In diesem Fall können Sie mit einem Statusbaum mit einigen festgelegten Animationsdetails das Fehlen eines lokalen Status umgehen, sodass Sie Animationen auslösen, die Animationen für eine bestimmte Dauer ausführen lassen und vieles mehr.

Das Wichtigste dabei ist, dass es zwei Möglichkeiten gibt, diese Animationen zu erstellen: Sie können sie als Teil Ihres Renderings ausführen und die Metapher "Benutzeroberfläche als Funktion des Status" intakt halten oder das DOM separat mutieren. Fast immer ist die bessere Antwort, einfach neu zu rendern, anstatt zu mutieren.


Nun zu einem Beispiel, in dem Sie die Möglichkeit haben, einen Animationsstatus lokal zu halten - ein Browsergame mit einer darauf basierenden reaktionsbasierten Benutzeroberfläche zu erstellen.

  • Normalerweise fahren Sie das Spiel mit Ticks ... die Spielzeit beginnt bei 0 und wird in jedem Frame erhöht. Kernspielanimationen können auf Leinwand oder in WebGL erstellt werden, wo es keinen lokalen Status gibt. Sie werden also die Startzeit und den Fortschritt der Animation basierend auf diesen Ticks festlegen.

Beispiel: Ein Sprite-Charakter hat eine Animation mit 10 Bildern, und Sie möchten die Animation über ca. 400 ms abspielen, sodass Sie das gezeichnete Sprite etwa alle 2 Ticks ändern.

Ihre Ticks können auch Zeitstempel sein, wenn Sie eine höhere Auflösung wünschen.

  • Ihr Spiel kann auch pausieren. In diesem Fall möchten Sie möglicherweise einige Animationen auf der Seite der React-Benutzeroberfläche anhalten.

In diesem Spielbeispiel möchten Sie Ihre Ticks nicht als Teil einer tick -Aktion erhöhen. Stattdessen möchten Sie die Ticks separat erhöhen, möglicherweise in einer beobachtbaren Struktur. Wenn Sie Redux-Aktionen wie Bewegungen oder Angriffe von Tastaturcharakteren auslösen, geben Sie das aktuelle Häkchen oder die aktuelle Uhrzeit an diese weiter, sodass Sie aufzeichnen können, bei welchem ​​Häkchen eine Aktion stattgefunden hat, und Ihre UI-Komponenten lokal aufzeichnen können, wann eine Animation begonnen hat .

Jetzt haben Sie den globalen Animationsstatus vom Interaktionsstatus getrennt. Wenn Sie eine "Wiedergabe" nur mit dem Redux-Status oder durch Wiederholen von Aktionen durchführen möchten, können Sie dies und Sie werden nicht durch das Erhöhen von Ticks als Teil Ihres Status belastet Baum.

Im Allgemeinen ist es auch viel besser, Animationen zu koordinieren, indem Start / Stopp-Zeiten durch Redux geleitet werden und Komponenten den Rest des Status lokal beibehalten.

Dies gilt nicht nur für Spiele. Jede erweiterte Sequenz von Animationen kann dies durchlaufen, z. B. wenn Sie mit React eine Reihe lang laufender Animationen auf einer Site erstellen möchten.

@jsonnull cool, das ist ein großartiges Beispiel, danke, dass

Ich würde einfach sagen - in Redux können Sie Ihre Statusobjekte mit Links zueinander einfach nicht speichern. Zum Beispiel - Benutzer können viele Beiträge haben und Beiträge können viele Kommentare haben, und ich möchte diese Objekte folgendermaßen speichern:

var user = {id: 'user1', posts: [], comments: []}
var post = {id: 'post1', user: user, comments: []}
user.posts.push(post);
var comment = {id: 'comment1', post: post, user: user}
post.coments.push(comment)
user.comments.push(comment)
appState.user = user

Redux erlaubt keine Verwendung von Objekthierarchien mit Zirkelverweisen. In Mobx (oder Cellx) können Sie einfach eine Eins-zu-Viele- und Viele-zu-Viele-Referenzen mit Objekten haben, was die Geschäftslogik um ein Vielfaches vereinfacht

@bgnorlov Ich glaube nicht, dass irgendetwas im redux -Paket Zirkelverweise verbietet, von denen Sie sprechen - sie machen es nur schwieriger, Ihre state in Ihrem Reduzierer zu klonen. Außerdem ist es möglicherweise einfacher, Statusänderungen mit Zirkelverweisen vorzunehmen, wenn Sie Immutable.js verwenden, um Ihren Status darzustellen, obwohl ich nicht sicher bin.

Sie können Beziehungen in Ihrem Redux-Status auch mithilfe von Fremdschlüsseln modellieren, obwohl dies meines Erachtens nicht so praktisch ist wie die Verwendung von ORM-ähnlichen Objektreferenzen:

var user = {id: 'user1', postIds: [], commentIds: []}
var post = {id: 'post1', userId: user.id, commentIds: []}
user.postIds.push(post.id);
var comment = {id: 'comment1', postId: post.id, userId: user.id}
post.commentIds.push(comment.id)
user.commentIds.push(comment.id)
appState.userId = user.id
appState.posts = {[post.id]: post}
appState.comments = {[comment.id]: comment}

// then join things like so:
var postsWithComments = _.map(appState.posts, post => ({
  ...post,
  comments: post.commentIds.map(id => appState.comments[id]),
})

@ jedwards1211 Um den Status mit
Beim Normalisierungsansatz benötigt ein Ereignishandler, wenn er Daten benötigt, jedes Mal ein Objekt anhand seiner ID aus dem globalen Status. Beispiel: Ich muss Aufgabengeschwister irgendwo im Ereignishandler filtern (Aufgaben können hierarchisch strukturiert sein). Mit Redux muss ich Thunk versenden, um Zugriff auf den App-Status zu erhalten

var prevTask = dispatch((_, getState)=>getState().tables.tasks[task.parentId]).children.map(childId=>dispatch((_, getState)=>getState().tables.tasks[childId])).filter(task=>...) [0]

oder mit Selektoren

var prevTask = dispatch(getTaskById(task.parentId)).children.map(childId=>dispatch(getTaskById(childId)).filter(task=>...)[0]

und dieses Boilerplate verwandelt Code in Chaos im Vergleich zur Mobx-Version, wenn ich einfach schreiben kann

var prevTask = task.parent.children.filter(task=>...)[0]

@ jedwards1211 , @bgnorlov : FWIW, das ist einer der Gründe, warum ich Redux-ORM mag. Sie können damit Ihren Redux-Speicher normalisieren, diese relationalen Aktualisierungen und Suchvorgänge jedoch vereinfachen. Tatsächlich ist dieses letzte Beispiel im Grunde identisch mit Redux-ORM.

Ich habe gerade ein paar Blog-Beiträge geschrieben, in denen die Grundlagen von Redux-ORM sowie die Kernkonzepte und die erweiterte Verwendung beschrieben werden .

@markerikson cool, danke für den Tipp!

@gaearon

Eine Sache zu beachten ist, dass wir nicht beabsichtigen, Redux für alle Zustände zu verwenden. Was auch immer für die App wichtig erscheint. Ich würde argumentieren, dass Eingaben und Animationsstatus von React (oder einer anderen kurzlebigen Statusabstraktion) behandelt werden sollten. Redux funktioniert besser für Dinge wie abgerufene Daten und lokal geänderte Modelle.

Ich fand diesen Kommentar so unglaublich nützlich. Ich weiß, dass ich zu spät zur Party komme, aber vielleicht ist das ein Teil meines Punktes. Über ein Jahr, seit dieser Kommentar veröffentlicht wurde, und dies ist immer noch der einzige Ort, an dem ich diese Idee zum Ausdruck gebracht habe.

Ausgehend von einem eckigen und entschieden nicht fluss- / reduxbezogenen Hintergrund ist es sehr schwierig, diese Idee selbst zu formulieren. Besonders wenn viele Beispiele immer noch Aktionen für jede Eingabe in einem Textfeld erstellen. Ich wünschte, jemand würde dieses Zitat in 50px-Text oben auf jeder Redux-Dokumentationsseite einfügen.

@leff Einverstanden. Ich hatte vor einiger Zeit genau diese Idee synthetisiert, aber sie wird nicht genug genannt. Selbst wenn Sie Redux für die Verlaufsverwaltung verwenden, gibt es häufig viele kurzlebige Status, die Ihre Statusverwaltung im Redux-Stil nicht belasten müssen.

Das ist nicht unbedingt eine Überraschung für Leute, die die Gelegenheit hatten, daran zu arbeiten, z. B. eine ausgereifte Desktop-App, die reichhaltiges Rückgängigmachen / Wiederherstellen ausführt. Aber für die Ozeane von Neulingen, die über Redux zu diesen Ideen kommen, wird es eine Offenbarung sein.

@bgnorlov das ist ein guter Punkt, dass es unmöglich ist,

Heutzutage neigen wir dazu, fast alles in Redux zu speichern, selbst wenn es sich um einen vorübergehenden ansichtsspezifischen Status handelt, den wir niemals außerhalb seines Kontexts verwenden (z. B. Status für eine redux-form -Instanz). In den meisten Fällen könnten wir einen solchen Zustand ohne Probleme aus dem Redux entfernen. Aber der Vorteil ist, dass es für den Fall da ist, dass wir jemals externe Komponenten benötigen, um in Zukunft darauf zu reagieren. Zum Beispiel könnten wir ein Symbol in der Navigationsleiste haben, das sich ändert, wenn ein Formular Fehler aufweist oder gesendet wird.

Ich würde sagen, das Wichtigste, das Sie beachten sollten, ist, dass das Einfügen des gesamten normalisierten Status in Redux (dh alles, was zum Rendern von Ansichten zusammengefügt werden muss) die Verwendung am einfachsten macht. Ein Zustand, der mit nichts verbunden werden muss, kann wahrscheinlich außerhalb von Redux leben, ohne dass Sie Schwierigkeiten haben, aber es tut normalerweise auch nicht weh, ihn in Redux zu setzen.

FWIW, die Dokumente weisen darauf hin, dass nicht alles in Redux gehen muss, laut http://redux.js.org/docs/faq/OrganizingState.html#organizing -state-only-redux-state.

In letzter Zeit wurde online viel darüber geredet, wofür Redux "geeignet" ist. Einige Leute sehen Vorteile darin, buchstäblich alles in Redux zu integrieren, andere empfinden dies als zu mühsam und möchten nur Daten speichern, die von einem Server abgerufen wurden. Es gibt hier also definitiv keine feste Regel.

Wie immer sind PRs herzlich willkommen, wenn Leute Ideen zur Verbesserung der Dokumente haben :)

Wenn Sie jemals Reduzierungen wiederverwenden und in Ihrem Redux-Status "Unterzustände" zuweisen müssen, habe ich ein Plugin entwickelt, um dies mühelos zu tun (mit React-Integration).

https://github.com/eloytoro/react-redux-uuid

@eloytoro IMO Wenn Sie Probleme mit einem Reduzierer eines Drittanbieters haben, der fest codierte Aktionstypen oder einen Ort im Bundesstaat hat, sollten Sie ein Problem im Projekt eröffnen. In Kürze wird es zu einer inakzeptablen Entwurfspraxis, wenn die Leute den wiederverwendbaren Reduzierer / die wiederverwendbare Aktion lernen Erstellermuster.

@eloytoro Ich wollte so etwas schreiben. Vielen Dank für den Hinweis!

@ jedwards1211 Ich denke du hast die falsche Idee. Ich habe keine Reduzierungen von Drittanbietern oder Probleme mit ihnen erwähnt, sondern nur versucht zu zeigen, wie mein Snippet das Problem lösen kann, Reduzierungen in Sammlungen mit dynamischen Größen wiederverwendbar zu machen

@avesus hoffe es funktioniert für dich

@eloytoro ah, das macht mehr Sinn.

Letztes Jahr habe ich mit Redux ein ziemlich komplexes Spiel erstellt. Ich habe auch an einem anderen Projekt teilgenommen, bei dem Redux nicht verwendet wird, sondern ein benutzerdefiniertes, minimalistisches Framework, das alles in UI-Stores und Server-Stores (einschließlich Eingaben) unterteilt. Ohne auf zu viele Details einzugehen, sind meine bisherigen Schlussfolgerungen folgende:

Einzelzustand ist immer besser, aber übertreibe es nicht. Es ist nur verwirrend und unnötig, jeden keyDown () durch den Store und zurück zur Ansicht zu bringen. Daher sollten Übergangszustände von lokalen Komponentenzuständen (wie z. B. React) behandelt werden.

Ich denke, da die Redux und Reaktion das beobachtbare Web entzündet haben, ist die Menge an guten Animationen auf den Seiten zurückgegangen.

@ mib32 du hast vielleicht recht! Hoffentlich gewöhnen sich die Leute irgendwann daran, in React gute Animationen zu erstellen.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen