Redux: Komponenten, die mit connect () nicht neu gerendert werden

Erstellt am 20. Aug. 2015  ·  32Kommentare  ·  Quelle: reduxjs/redux

Ich lerne Redux kennen, indem ich die Dokumente gelesen habe und beginne damit, nur das reale Beispiel zu modifizieren. In meinem Fall habe ich die GitHub-HTTP-API-Aufrufe gegen meine eigenen ausgetauscht und die Repo- und Benutzerkonzepte gegen analoge in meiner Umgebung ausgetauscht. Sollte bisher nicht zu unterschiedlich sein, oder?

Hier habe ich Probleme:

Meine API-Aufrufe sind erfolgreich, die Antwort JSON wird camelCased und normalisiert, aber meine Komponente wird nicht erneut gerendert, nachdem die API-Anforderung erfolgreich ausgeführt wurde. Kurz nachdem USER_REQUEST versandt wurde und die Benutzerkomponente aktualisiert wurde, um "Loading ..." anzuzeigen, wird USER_SUCCESS versandt, und sein nächster Status enthält einen ausgefüllten entities Schlüssel:

pasted_image_8_19_15__3_27_pm

Das würde bedeuten, dass das Problem in der Middleware liegt, oder? Ich sollte die Entitäten aus der Aktion im Speicher speichern, und die Komponente sollte die Änderung beobachten und neu rendern, ja?

Wo werden im realen Beispiel erfolgreich abgerufene und normalisierte Daten tatsächlich in den Speicher gestellt? Ich sehe, wo es normalisiert wird, aber nicht, wo das Ergebnis dieser Normalisierung in den Laden geliefert wird.

Vielen Dank!

question

Hilfreichster Kommentar

Daten werden im Speicher über die Ergebnisse von Handhabungsaktionen in Reduzierungen festgelegt / aktualisiert / gelöscht. Reduzierer erhalten den aktuellen Status eines Teils Ihrer App und erwarten, dass sie einen neuen Status erhalten. Einer der häufigsten Gründe dafür, dass Ihre Komponenten möglicherweise nicht erneut gerendert werden, besteht darin, dass Sie den vorhandenen Status in Ihrem Reduzierer ändern, anstatt eine neue Kopie des Status mit den erforderlichen Änderungen zurückzugeben ( siehe Abschnitt Fehlerbehebung ). Wenn Sie den vorhandenen Status direkt ändern, erkennt Redux keinen Statusunterschied und benachrichtigt Ihre Komponenten nicht, dass sich der Speicher geändert hat.

Ich würde also auf jeden Fall Ihre Reduzierungen überprüfen und sicherstellen, dass Sie den vorhandenen Zustand nicht mutieren. Hoffentlich hilft das!

Alle 32 Kommentare

Daten werden im Speicher über die Ergebnisse von Handhabungsaktionen in Reduzierungen festgelegt / aktualisiert / gelöscht. Reduzierer erhalten den aktuellen Status eines Teils Ihrer App und erwarten, dass sie einen neuen Status erhalten. Einer der häufigsten Gründe dafür, dass Ihre Komponenten möglicherweise nicht erneut gerendert werden, besteht darin, dass Sie den vorhandenen Status in Ihrem Reduzierer ändern, anstatt eine neue Kopie des Status mit den erforderlichen Änderungen zurückzugeben ( siehe Abschnitt Fehlerbehebung ). Wenn Sie den vorhandenen Status direkt ändern, erkennt Redux keinen Statusunterschied und benachrichtigt Ihre Komponenten nicht, dass sich der Speicher geändert hat.

Ich würde also auf jeden Fall Ihre Reduzierungen überprüfen und sicherstellen, dass Sie den vorhandenen Zustand nicht mutieren. Hoffentlich hilft das!

Stellen Sie zusätzlich zu den mapStateToProps in Ihrer Funktion connect(mapStateToProps)(YourConnectedComponent) den relevanten Status (in diesem Fall entities ) abbildet, damit die verbundene Komponente lauscht zu diesem Stück des Staates.

Beachten Sie auch, dass connect() einen flachen Vergleich durchführt: https://github.com/rackt/react-redux/blob/d5bf492ee35ad1be8ffd5fa6be689cd74df3b41e/src/components/createConnect.js#L91

Da sich die Form Ihres Verbindungsstatus nicht geändert hat, wird wahrscheinlich kein Update durchgeführt.

Ah, verstanden! Mein mapStateToProps versucht, ein Objekt aus dem Status zu extrahieren, aber nicht den richtigen Schlüssel angegeben. Anstatt entities.users[login] extrahieren, hatte ich es auf entities.users[id] . Das entities.users -Objekt wurde mit einem login -String verschlüsselt. Wenn also die numerische id nicht gefunden wurde, wurden keine Requisiten geändert, sodass kein erneutes Rendern erforderlich war.

Vielen Dank für die Hilfe! Ich schätze die Zeit, die Sie für Ihre Unterstützung gebraucht haben, sehr.

@ Timdorr
Das ist lächerlich. Wie erzwinge ich connect() um tiefgreifende Änderungen zu überprüfen? Andernfalls müssen Dutzende von Containerkomponenten für jede tiefere Statusebene erstellt werden. Es ist nicht normal

Sie sollten unbedingt viele verbundene Komponenten in einer App mit angemessener Größe haben. Wenn Sie eine einzelne verbundene Komponente der obersten Ebene haben, werden Sie große Teile Ihrer App jedes Mal unnötig neu rendern, wenn sich der Status ändert. Und Sie werden eine der primären Optimierungen von React-Redux vermeiden, die darin besteht, nur dann neu zu rendern, wenn sich der Status ändert, für den sie abonniert wurde.

Wenn Sie eine komplexe Statusabfrage auswerten müssen, verwenden Sie reselect, um dies zu beschleunigen.

@timdorr @wzup

Es kann hilfreich sein zu verdeutlichen, dass eine Verbindung an tieferen Stellen im Baum nicht unbedingt erforderlich ist, um das Problem des tiefen Vergleichs zu lösen.

Das Verbinden an niedrigeren Stellen in Ihrem Komponentenbaum kann zwar zur Leistung beitragen, ist jedoch nicht die grundlegende Lösung für das Problem des tiefen Vergleichs. Die Lösung für tiefgreifende Vergleichsprobleme besteht darin, dass Ihre Reduzierer den Zustand unveränderlich aktualisieren, sodass ein flacher Vergleich ausreicht, um zu wissen, wann sich ein tieferer Zustand geändert hat.

Mit anderen Worten, das Verbinden einer Komponente der obersten Ebene mit einem großen verschachtelten Statuselement funktioniert auch bei flachen Vergleichen einwandfrei, solange Ihre Reduzierungen einen neuen Status zurückgeben, ohne vorhandene Statusobjekte zu mutieren.

@naw Danke für diesen Tipp. Auf dieser Grundlage habe ich einen neuen Reduzierer hinzugefügt, bei dem dieses Problem aufgetreten ist:

const reducers = combineReducers({
    //...bunch of reducers which always return objects of 3 or 4 properties that change sometimes but
    // the shape stays the same
    dataVersion: (state = Symbol(), action = {}) => {
        switch (action.type) {
            case SAME_ACTION_AS_THE_ONE_THAT_UPDATES_A_COMPLEX_PROPERTY:
            case ANOTHER_ACTION_THAT_ALSO_UPDATES_A_COMPLEX_PROPERTY:
                return Symbol():
            default:
                return state;
        }
    }
})

Das fühlt sich für mich trotzdem komisch an. Wenn ich einen Reduzierer habe, der geht:

    switch (action.type) {
       case UPDATE_THIS:
           return {a: action.a, b: action.b, c: action.c};
       default:
           return state;
     }

Die Tatsache, dass ich ein neu initialisiertes Objekt zurückgebe, das zufällig dieselbe Form wie der vorherige Status hat, sollte ausreichen, um ein previousState !== newState auszulösen. Wenn ich jedes Mal unveränderliche Daten zurückgeben soll, scheint es ineffizienter zu sein, einen flachen Vergleich der Form und nicht nur der Identität durchzuführen. (Angenommen, das ist es, was Redux tatsächlich tut. Die Tatsache, dass meine Symbol Problemumgehung funktioniert, lässt mich denken, dass das so ist.)

In diesem speziellen Fall löst das Hinzufügen weiterer Containerkomponenten das Problem nicht. Leider kann ich zur Klärung keinen Link zu einem ganzen Github-Projekt erstellen, da dies das Closed-Source-Material meines Unternehmens ist, mit dem ich mich hier befasse.

@Zacqary Der Vergleich des aktuellen Status mit dem vorherigen Status erfolgt mit striktem Gleichwert (===). Der Vergleich von stateProps (das Ergebnis von mapStateToProps) erfolgt mit flach gleich.

@ Zacqary

mapStateToProps "sammelt" Daten für Ihre verbundene Komponente, indem Sie in den Speicher greifen.

mapStateToProps zieht verschiedene Teile aus dem Geschäft und weist sie den Schlüsseln in einem neuen stateProps -Objekt zu.

Das resultierende stateProps -Objekt ist jedes Mal neu (daher ändert sich seine Identität), sodass wir nicht einfach die Objektidentität des stateProps -Objekts selbst vergleichen können - wir müssen ein Objekt entfernen Level und überprüfen Sie die Objektidentität jedes Werts, wo shallowEqual ins Spiel kommt, wie @jimbolla sagte.

Typischerweise treten Probleme auf, wenn Sie Reduzierungen so schreiben, dass sich die Objektidentität eines Zustands nicht ändert, während sich ein verschachteltes Attribut darin ändert. Dies ist ein mutierender Zustand, anstatt einen neuen Zustand unveränderlich zurückzugeben, was dazu führt, dass react-redux denkt, dass sich nichts geändert hat, obwohl sich tatsächlich etwas geändert hat.

Die Tatsache, dass ich ein neu initialisiertes Objekt zurückgebe, das zufällig dieselbe Form wie der vorherige Status hat, sollte ausreichen, um einen vorherigen Status auszulösen! == newState.

Wenn Sie ein neues Objekt für einen Statusabschnitt zurückgeben und mapStateToProps diesen Statusabschnitt als Wert für einen seiner Schlüssel verwendet, erkennt react-redux , dass sich die Objektidentität geändert hat, und wird dies auch tun ein erneutes Rendern nicht verhindern.

Gibt es etwas Bestimmtes, das nicht so funktioniert, wie Sie es erwarten? Ich bin mir nicht sicher, was Sie mit Ihrer Symbol Problemumgehung meinen.

Es lohnt sich zu überlegen, ob die zusätzliche Komponente einfach in die connected() -Komponente verschoben werden soll, um die Requisiten zu erben. Ich bin zu diesem Problem gekommen, indem ich versucht habe, zwei Komponenten mit connect() , aber das war unnötige Komplexität.

@ernieturner Ich weiß, dass dies alt ist, aber Sie möchten möglicherweise den Link zum Abschnitt "Fehlerbehebung" aktualisieren.

Prost

Ich hatte dieses Problem, nicht genau.
Ich habe die Eigenschaften angewendet, um Folgendes anzugeben:

  constructor(props){
    this.state = {
      text: props.text
    }
  }

Und es hat nicht funktioniert. Also habe ich Wert direkt von Requisiten wie angewendet

{this.props.text}

Und es funktioniert gut :)

@AlexanderKozhevin Wenn ich mich nicht irre, fehlt Ihrem Konstruktor ein super(props) .
Normalerweise dürfen Sie nicht einmal this bevor Sie super(props) aufrufen.

@ cdubois-mh Richtig, danke für die Notiz.

Ich habe einen maßgeschneiderten App Root Reducer geschrieben und bekomme dieses Problem.
Rückgabe einer Kopie des gesamten Status
{...state}
Am Ende hat mir der Wurzelreduzierer geholfen

@daedalius So soll ein Reduzierer funktionieren.
Es sollte den Status zurückgeben, wenn die Aktion irrelevant ist, oder eine Kopie des Status mit den erwarteten geänderten Werten zurückgeben.

Wenn Sie keine Kopie zurückgeben, kann reag nicht immer feststellen, dass sich ein Wert geändert hat.
(Wenn Sie beispielsweise einen verschachtelten Eintrag ändern)

Überprüfen Sie diesen Zustand:

{
    "clients": [
        { "name": "John", "cid": 4578 },
        { "name": "Alex", "cid": 5492 },
        { "name": "Bob",  "cid": 254 }
    ]
}

Wenn Sie den Namen eines Clients ändern, aber keinen Klon des Status zurückgeben, haben Sie den Status nicht geändert, sondern das Objekt im Array geändert.

Das Objekt selbst hat immer noch dieselbe Referenz wie zuvor, sodass bei einer Reaktion die Änderung übersehen wird.
Wenn Sie einen Klon zurückgeben, unterscheidet sich die Referenz des Status selbst (da es sich um ein neues Objekt handelt). Durch die Reaktion wird der gesamte Prozess des erneuten Renderns eingeleitet.

Wenn ich falsch liege, korrigieren Sie mich bitte, aber so verstehe ich, dass Reduzierungen funktionieren.

Ja, das ist richtig. Beachten Sie außerdem, @daedalius , dass Sie nicht immer eine flache Kopie von state erstellen möchten - Sie sollten dies nur tun, wenn sich etwas geändert hat, da Sie sonst möglicherweise Ihre Komponenten unnötig neu rendern müssen.

Update erzwingen

<AfterConnect
    _forceUpdate={Symbol()}
/>

@BrookShuihuaLee Was soll das heißen? Sie haben keine Informationen oder Erklärungen zu Ihrem Code angegeben. Was tut es? Warum ist es für die aktuelle Diskussion relevant?

@CedSharp Die Funktion flatEqual gibt false zurück, wenn _forceUpdate = {Symbol ()} festgelegt wird.

@BrookShuihuaLee Wäre großartig, wenn Sie eine Erklärung wie diese in Ihre erste Antwort aufnehmen könnten, damit die Leute sie besser verstehen ^^

@ CedSharp ja

@BrookShuihuaLee : Das scheint eine schlechte Idee zu sein. Warum sollten Sie das tun wollen?

@BrookShuihuaLee , ich stimme @markerikson zu .
Es gibt einen Grund, warum Sie KEINEN Klon zurückgeben sollten, wenn sich der Status nicht geändert hat. Sofern Sie kein gültiges Beispiel angeben können, in dem Ihre Lösung notwendig erscheint, würde ich dringend davon abraten, die Funktionsweise von Redux zu umgehen.

@ CedSharp
@markerikson
Weil die Komponente AfterConnect vom Design her häufig neu gerendert wird. Es ist in Ordnung, es erneut zu rendern, wenn die übergeordnete Komponente erneut gerendert wird. Ich möchte nicht alle Berechnungen in der übergeordneten Komponente durchführen und Tonnen von Requisiten für AfterConnect festlegen.

@BrookShuihuaLee , es tut mir leid, aber wie ist Ihre Komponente für die
Wenn ich das richtig verstehe, sprechen Sie über Ihre eigene benutzerdefinierte Komponente? ForceUpdate ist etwas sehr Entmutigtes in der React-Welt und ich würde es nicht als Lösung empfehlen.

Stattdessen könnten Sie eine beobachtbare Lösung verwenden, bei der Sie nach Änderungen suchen, die nicht am Status liegen. Ich weiß nicht, warum Sie diese bestimmte Komponente neu rendern müssen, ohne dass der Status geändert wird, aber wenn Sie wissen, warum sie neu gerendert werden sollte, könnten Sie vielleicht so etwas wie mobx verwenden?
(https://github.com/mobxjs/mobx)

Ich versuche zu verstehen, was Ihre Lösung ist und warum sie in dieser Situation gilt.

@CedSharp Ich meine nicht, dass jeder forceUpdate verwenden sollte. Aber die Empfehlung ist eine Sache, die Wahl ist eine andere. React behält auch das ReactComponent.prototype.forceUpdate für Entwickler. Die Menschen brauchen Entscheidungen.

Ich habe versucht, eine andere Wahl zu treffen. Vielleicht ist es nicht die beste Wahl.

Nicht optimal, aber ich bin auf dieses Problem gestoßen, weil ich meinen App-Status falsch strukturiert habe. Ich habe das Problem des flachen Vergleichs umgangen, indem ich nur ein flaches Feld auf dem Teil des Zustands aktualisiert habe, der mir Probleme bereitet hat.

case SOME_ACTION:
      return {
        ...state,
        ts: (new Date).getTime(),
      };

IMO, keine gute Lösung, aber für alle, die sich derzeit in einer Bindung befinden (dh Sie müssen das Ende des Tages veröffentlichen), ist dies eine schnelle Lösung, und Sie müssen kein Rendern an einer anderen Stelle erzwingen.

Der Code sollte überarbeitet werden, damit ein flacher Vergleich auch in einem tief verschachtelten Zustand funktioniert.

Der Staat muss unveränderlich sein. Kopieren Sie den Status in Reducer tief, dann ist ein flacher Vergleich in Connect ausreichend und effektiv.

Mein Problem war, ein Objekt mit Arrays darin zu haben. Da es sich um einen flachen Vergleich handelte, erreichte es nie die Objekte in den untergeordneten Arrays. Ich habe es gelöst, indem ich in mapStateToProps 👍🏼 list:state.obj.list anstelle von lists:state.obj verwendet habe

Ich bin gerade auf dieses Problem gestoßen, und nachdem ich die ersten paar Antworten gelesen hatte, überprüfte ich meinen Reduzierer und bemerkte, dass ich dort einen Tippfehler hatte. Ich habe eine Eigenschaft umbenannt und INITIAL_STATE aktualisiert, aber vergessen, den Schlüssel zu ändern, der durch diese problematische Aktion aktualisiert wurde.

Ich habe im Grunde genommen einen zufälligen Schlüssel aktualisiert, der nicht in der Komponente verwendet wurde, sodass er offensichtlich nicht erneut gerendert wurde, wenn dieser zufällige Schlüssel aktualisiert wurde.

PRÜFEN SIE IHREN REDUZIERER AUF TYPEN! :) :)

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen