Woher weiß eine mit Redux verbundene Komponente, wann sie erneut gerendert werden muss?

85

Ich vermisse wahrscheinlich etwas sehr Offensichtliches und möchte mich klären.

Hier ist mein Verständnis.
In einer naiven Reaktionskomponente haben wir states& props. Durch das Aktualisieren statemit wird setStatedie gesamte Komponente neu gerendert. propssind meistens schreibgeschützt und eine Aktualisierung macht keinen Sinn.

In einer Reaktionskomponente, die einen Redux-Speicher abonniert store.subscribe(render), wird sie offensichtlich bei jeder Aktualisierung des Speichers neu gerendert.

React-Redux hat einen Helfer connect(), der einen Teil des Statusbaums (der für die Komponente von Interesse ist) und actionCreators propsfür die Komponente injiziert , normalerweise über so etwas

const TodoListComponent = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

Aber mit dem Verständnis, dass a setStatewichtig ist TodoListComponent, um auf Änderungen des Redux-Statusbaums (erneutes Rendern) zu reagieren, kann ich keinen stateoder setStateverwandten Code in der TodoListKomponentendatei finden. Es liest sich ungefähr so:

const TodoList = ({ todos, onTodoClick }) => (
  <ul>
    {todos.map(todo =>
      <Todo
        key={todo.id}
        {...todo}
        onClick={() => onTodoClick(todo.id)}
      />
    )}
  </ul>
)

Kann mich jemand in die richtige Richtung weisen, was mir fehlt?

PS Ich folge dem mit dem Redux-Paket gelieferten ToDo-Listenbeispiel .

Joe Lewis
quelle

Antworten:

109

Die connectFunktion generiert eine Wrapper-Komponente, die den Speicher abonniert. Wenn eine Aktion ausgelöst wird, wird der Rückruf der Wrapper-Komponente benachrichtigt. Anschließend wird Ihre mapStateFunktion ausgeführt und das Ergebnisobjekt aus dieser Zeit flach mit dem Ergebnisobjekt aus der letzten Zeit verglichen. Wenn Sie also ein Redux-Speicherfeld mit demselben Wert neu schreiben würden, würde dies kein erneutes Rendern auslösen. Wenn die Ergebnisse unterschiedlich sind, werden die Ergebnisse als Requisiten an Ihre "echte" Komponente "übergeben.

Dan Abramov hat eine großartige vereinfachte Version von connectat ( connect.js ) geschrieben, die die Grundidee veranschaulicht, obwohl sie keine der Optimierungsarbeiten zeigt. Ich habe auch Links zu einer Reihe von Artikeln über die Redux-Leistung , in denen einige verwandte Ideen diskutiert werden.

aktualisieren

React-Redux v6.0.0 hat einige wichtige interne Änderungen daran vorgenommen, wie verbundene Komponenten ihre Daten aus dem Store erhalten.

Als Teil davon habe ich einen Beitrag geschrieben, in dem erklärt wird, wie die connectAPI und ihre Interna funktionieren und wie sie sich im Laufe der Zeit geändert haben:

Idiomatischer Redux: Die Geschichte und Implementierung von React-Redux

markerikson
quelle
Vielen Dank! Sind Sie dieselbe Person, die mir neulich geholfen hat @ HN - news.ycombinator.com/item?id=12307621 mit nützlichen Links? Schön dich kennenzulernen :)
Joe Lewis
4
Ja, das bin ich :) Ich verbringe viel zu viel Zeit damit, online nach Diskussionen über Redux zu suchen - Reddit, HN, SO, Medium und verschiedene andere Orte. Froh, dass ich Helfen kann! (Auch zu Ihrer Information, ich empfehle die Reactiflux-Chat-Kanäle auf Discord sehr. Viele Leute hängen da rum und beantworten Fragen. Ich bin normalerweise abends online, US EST. Der Einladungslink ist bei reactiflux.com .)
markerikson
Angenommen, dies hat die Frage beantwortet, möchten Sie die Antwort akzeptieren? :)
markerikson
Vergleicht es die Objekte selbst oder die Eigenschaften der Vorher / Nachher-Objekte? Wenn es das letztere ist, überprüft es nur die erste Ebene. Ist es das, was Sie mit "flach" meinen?
Andy
Ja, eine "flache Gleichheitsprüfung" bedeutet, die Felder der ersten Ebene jedes Objekts zu vergleichen : prev.a === current.a && prev.b === current.b && ...... Dies setzt voraus, dass Datenänderungen zu neuen Referenzen führen, was bedeutet, dass unveränderliche Datenaktualisierungen anstelle einer direkten Mutation erforderlich sind.
markerikson
13

Meine Antwort ist etwas außerhalb des linken Feldes. Es beleuchtet ein Problem, das mich zu diesem Beitrag geführt hat. In meinem Fall schien die App nicht neu gerendert zu werden, obwohl sie neue Requisiten erhielt.
React-Entwickler hatten eine Antwort auf diese häufig gestellte Frage, die besagt, dass 99% der Zeit, in der React nicht erneut gerendert wird, wenn der (Store) mutiert ist. Noch nichts über die anderen 1%. Mutation war hier nicht der Fall.


TLDR;

componentWillReceivePropsSo statekann das mit dem Neuen synchronisiert werden props.

Edge - Fall: Sobald stateUpdates, dann die App nicht wieder machen!


Es stellt sich heraus, dass Ihre App, wenn sie nur statezum Anzeigen ihrer Elemente verwendet wird, propsaktualisiert werden kann, aber statenicht, sodass kein erneutes Rendern erforderlich ist.

Ich hatte statedas abhängig propsvon Redux erhalten store. Die Daten, die ich brauchte, waren noch nicht im Laden, also habe ich sie componentDidMountwie üblich abgerufen . Ich habe die Requisiten zurückbekommen, als mein Reduzierer den Speicher aktualisiert hat, da meine Komponente über mapStateToProps verbunden ist. Die Seite wurde jedoch nicht gerendert, und der Status war immer noch voller leerer Zeichenfolgen.

Ein Beispiel hierfür ist, dass ein Benutzer eine Seite "Beitrag bearbeiten" aus einer gespeicherten URL geladen hat. Sie haben über die postIdURL Zugriff auf die , aber die Informationen sind noch storenicht vorhanden, sodass Sie sie abrufen. Die Elemente auf Ihrer Seite sind gesteuerte Komponenten. Alle angezeigten Daten befinden sich also in state.

Mit Redux wurden die Daten abgerufen, der Speicher wurde aktualisiert und die Komponente wurde connectbearbeitet, aber die App spiegelte die Änderungen nicht wider. Bei näherer Betrachtung propswurden empfangen, aber App nicht aktualisiert. statenicht aktualisiert.

Nun, propswird aktualisiert und verbreitet, aber statenicht. Sie müssen speziell angeben state, um zu aktualisieren.

Sie können dies nicht tun render()und componentDidMounthaben die Zyklen bereits beendet.

componentWillReceivePropsHier aktualisieren Sie stateEigenschaften, die von einem geänderten propWert abhängen .

Anwendungsbeispiel:

componentWillReceiveProps(nextProps){
  if (this.props.post.category !== nextProps.post.category){
    this.setState({
      title: nextProps.post.title,
      body: nextProps.post.body,
      category: nextProps.post.category,
    })
  }      
}

Ich muss diesem Artikel einen Gruß aussprechen, der mich über die Lösung aufgeklärt hat, die Dutzende anderer Posts, Blogs und Repos nicht erwähnt haben. Alle anderen, die Schwierigkeiten hatten, eine Antwort auf dieses offensichtlich dunkle Problem zu finden, hier ist es:

ReactJs Komponentenlebenszyklusmethoden - Ein tiefer Tauchgang

componentWillReceivePropsHier werden Sie aktualisieren state, um mit den propsUpdates synchron zu bleiben .

Sobald stateUpdates, dann Felder je nach state Aufgaben wieder machen!

SherylHohman
quelle
3
Ab React 16.3ab, Sie sollten die Verwendung getDerivedStateFromPropsstatt componentWillReceiveProps. Aber in der Tat sollten Sie nicht einmal das alles tun, wie hier
kyw
Es funktioniert nicht, wenn sich der Status ändert, dann funktioniert componentWillReceiveProps so oft, dass ich versuche, dass dies nicht funktioniert. Insbesondere mehrstufige verschachtelte oder komplexe Requisiten, daher muss ich der Komponente eine ID zuweisen und eine Änderung dafür auslösen und den Status aktualisieren, um neue Daten erneut zu rendern
May Weather VN
0

Diese Antwort ist eine Zusammenfassung von Brian Vaughns Artikel mit dem Titel " Sie brauchen wahrscheinlich keinen abgeleiteten Zustand" (07. Juni 2018).

Das Ableiten des Zustands von Requisiten ist ein Anti-Muster in all seinen Formen. Einschließlich der Verwendung der älteren componentWillReceivePropsund der neueren getDerivedStateFromProps.

Betrachten Sie die folgenden Lösungen, anstatt den Status von Requisiten abzuleiten.

Zwei Best-Practice-Empfehlungen

Empfehlung 1. Voll gesteuerte Komponente
function EmailInput(props) {
  return <input onChange={props.onChange} value={props.email} />;
}
Empfehlung 2. Vollständig unkontrollierte Komponente mit einem Schlüssel
// parent class
class EmailInput extends Component {
  state = { email: this.props.defaultEmail };

  handleChange = event => {
    this.setState({ email: event.target.value });
  };

  render() {
    return <input onChange={this.handleChange} value={this.state.email} />;
  }
}

// child instance
<EmailInput
  defaultEmail={this.props.user.email}
  key={this.props.user.id}
/>

Zwei Alternativen, wenn die Empfehlungen aus irgendeinem Grund für Ihre Situation nicht funktionieren.

Alternative 1: Setzen Sie die unkontrollierte Komponente mit einer ID-Stütze zurück
class EmailInput extends Component {
  state = {
    email: this.props.defaultEmail,
    prevPropsUserID: this.props.userID
  };

  static getDerivedStateFromProps(props, state) {
    // Any time the current user changes,
    // Reset any parts of state that are tied to that user.
    // In this simple example, that's just the email.
    if (props.userID !== state.prevPropsUserID) {
      return {
        prevPropsUserID: props.userID,
        email: props.defaultEmail
      };
    }
    return null;
  }

  // ...
}
Alternative 2: Setzen Sie die unkontrollierte Komponente mit einer Instanzmethode zurück
class EmailInput extends Component {
  state = {
    email: this.props.defaultEmail
  };

  resetEmailForNewUser(newEmail) {
    this.setState({ email: newEmail });
  }

  // ...
}
Lass mich darüber basteln
quelle
-2

Da ich nur weiß, was Redux tut, ruft bei Änderung des Geschäftsstatus componentWillRecieveProps auf, wenn Ihre Komponente vom mutierten Status abhängig war, und dann sollten Sie Ihre Komponente zwingen, ihren Status so zu aktualisieren

1-Store-Statusänderung-2-Aufruf (componentWillRecieveProps (() => {3-Komponenten-Statusänderung}))

ghazaleh javaheri
quelle