ReactJS state vs prop

121

Dies mag die Grenze zwischen verantwortungsbewusst und eigensinnig überschreiten, aber ich gehe hin und her, wie eine ReactJS-Komponente mit zunehmender Komplexität strukturiert werden kann und eine Richtung verwenden könnte.

Aus AngularJS kommend möchte ich mein Modell als Eigenschaft an die Komponente übergeben und die Komponente das Modell direkt ändern lassen. Oder sollte ich das Modell in verschiedene stateEigenschaften aufteilen und beim Zusammensenden wieder zusammenstellen? Was ist der ReactJS-Weg?

Nehmen Sie das Beispiel eines Blogpost-Editors. Der Versuch, das Modell direkt zu ändern, sieht folgendermaßen aus:

var PostEditor = React.createClass({
  updateText: function(e) {
    var text = e.target.value;
    this.props.post.text = text;
    this.forceUpdate();
  },
  render: function() {
    return (
      <input value={this.props.post.text} onChange={this.updateText}/>
      <button onClick={this.props.post.save}/>Save</button>
    );
  }
});

Welches scheint falsch.

Ist es eher die React-Methode, um unsere textModelleigenschaft zu stateerstellen und sie vor dem Speichern wieder in das Modell zu kompilieren?

var PostEditor = React.createClass({
  getInitialState: function() {
    return {
      text: ""
    };
  },
  componentWillMount: function() {
    this.setState({
      text: this.props.post.text
    });
  },
  updateText: function(e) {
    this.setState({
      text: e.target.value
    });
  },
  savePost: function() {
    this.props.post.text = this.state.text;
    this.props.post.save();
  },
  render: function() {
    return (
      <input value={this.state.text} onChange={this.updateText}/>
      <button onClick={this.savePost}/>Save</button>
    );
  }
});

Dies erfordert keinen Aufruf von this.forceUpdate(), aber wenn das Modell wächst (ein Beitrag kann einen Autor, einen Betreff, Tags, Kommentare, Bewertungen usw. enthalten), wird die Komponente sehr kompliziert.

Ist die erste Methode mit ReactLink der richtige Weg?

Nikolaus
quelle

Antworten:

64

Ihr zweiter Ansatz ist eher so. React kümmert sich weniger um Modelle als um Werte und deren Fluss durch Ihre App. Im Idealfall wird Ihr Post-Modell in einer einzelnen Komponente im Stammverzeichnis gespeichert. Anschließend erstellen Sie untergeordnete Komponenten, die jeweils Teile des Modells verbrauchen.

Sie können Rückrufe an die untergeordneten Elemente weiterleiten, die Ihre Daten ändern müssen, und sie über die untergeordnete Komponente aufrufen.

Das direkte Ändern von this.props oder this.state ist keine gute Idee, da React die Änderungen nicht übernehmen kann. Das liegt daran, dass React einen flachen Vergleich Ihrer Post-Requisite durchführt, um festzustellen, ob sie sich geändert hat.

Ich habe diese jsfiddle gemacht, um zu zeigen, wie Daten von einer äußeren zu einer inneren Komponente fließen können.

Die handleClickMethode zeigt drei Möglichkeiten, um den Status (im) ordnungsgemäß zu aktualisieren:

var Outer = React.createClass({

  getInitialState: function() {
    return {data: {value: 'at first, it works'}};
  },

  handleClick: function () {

    // 1. This doesn't work, render is not triggered.
    // Never set state directly because the updated values
    // can still be read, which can lead to unexpected behavior.

    this.state.data.value = 'but React will never know!';

    // 2. This works, because we use setState

    var newData = {value: 'it works 2'};
    this.setState({data: newData});

    // 3. Alternatively you can use React's immutability helpers
    // to update more complex models.
    // Read more: http://facebook.github.io/react/docs/update.html

    var newState = React.addons.update(this.state, {
      data: {value: {$set: 'it works'}}
    });
    this.setState(newState);
 },

  render: function() {
      return <Inner data={this.state.data} handleClick={this.handleClick} />;
  }
});
jxg
quelle
Aber was machen wir, wenn wir ein undurchsichtiges Modell mit eigenen Zustandsmanipulationsfunktionen haben? Angenommen text, wir haben anstelle eines Feldes eine setText Methode, die die Validierung und einige andere Dinge ausführt. Ich kann sehen, dass Methode (2) funktioniert, wenn sie setTextrein ist und eine brandneue Instanz des Modells zurückgibt. Wenn setTextwir jedoch nur den inneren Zustand aktualisieren, müssten wir immer noch aufrufen forceUpdate, oder?
Hugomg
1
Ja, Sie könnten anrufen forceUpdate, aber an diesem Punkt "lecken" Sie aus React heraus. Es ist möglicherweise besser, einen setState()Rückruf an das undurchsichtige Modell zu übergeben, um zu vermeiden, dass erneutes Rendern manuell ausgelöst werden muss.
jxg
Ich bin mir immer noch nicht sicher, ob ich alles verstehe. Also muss jede Komponente, die Daten ändern soll, eine tiefe Kopie der übergebenen Requisiten erstellen? Ändern Sie diese Kopie und senden Sie sie anschließend, um die Originaldaten nicht zu ändern. Irgendwann fließt die Änderung bis zum Stammverzeichnis, wo sie behandelt wird, und die gesamte App wird erneut gerendert. Ist das richtig?
Nicholas
97

Aktualisierung 2016: Die Reaktion wurde geändert und die Erklärung "Requisiten gegen Status" wurde sehr einfach. Wenn eine Komponente Daten ändern muss, versetzen Sie sie in einen Zustand, andernfalls in Requisiten. Weil Requisiten schreibgeschützt sind jetzt .

Was ist der genaue Unterschied zwischen Requisiten und Staat?

Sie können eine gute Erklärung finden hier (Vollversion)

Requisiten und Zustand ändern

mrvol
quelle
1
Tatsächlich könnte setProps () Requisiten innerhalb einer Komponente ändern und ein erneutes Rendern auslösen
WaiKit Kung
2
setPropsist veraltet und sollte nicht verwendet werden. Durch Ersetzen wird die Komponente erneut gerendert und React kann die Unterschiede behandeln.
jdmichal
Und wenn Sie nach einem
Erklärvideo
35

Aus React doc

Requisiten sind unveränderlich: Sie werden vom Elternteil weitergegeben und sind "im Besitz" des Elternteils. Um Interaktionen zu implementieren, führen wir einen veränderlichen Zustand in die Komponente ein. this.state ist für die Komponente privat und kann durch Aufrufen von this.setState () geändert werden. Wenn der Status aktualisiert wird, rendert sich die Komponente neu.

Von TrySpace : Wenn Requisiten (oder Status) aktualisiert werden (über setProps / setState oder Parent), wird die Komponente ebenfalls neu gerendert.

Fizer Khan
quelle
16

Eine Lesung aus Thinking in React :

Lassen Sie uns jeden einzelnen durchgehen und herausfinden, welcher Zustand ist. Stellen Sie einfach drei Fragen zu jedem Datenelement:

  1. Wird es von einem Elternteil über Requisiten weitergegeben? Wenn ja, ist es wahrscheinlich kein Staat.
  2. Ändert es sich im Laufe der Zeit? Wenn nicht, ist es wahrscheinlich kein Staat.

  3. Können Sie es basierend auf einem anderen Status oder Requisiten in Ihrer Komponente berechnen? Wenn ja, ist es kein Zustand.

onmyway133
quelle
13

Ich bin nicht sicher, ob ich Ihre Frage beantworte, aber ich habe festgestellt, dass das Container- / Komponentenmuster besonders in einer großen / wachsenden Anwendung unglaublich gut funktioniert.

Im Wesentlichen haben Sie zwei React-Komponenten:

  • eine "reine" Anzeigekomponente, die sich mit Styling und DOM-Interaktion befasst;
  • Eine Containerkomponente, die sich mit dem Zugriff auf / Speichern externer Daten, der Verwaltung des Status und dem Rendern der Anzeigekomponente befasst.

Beispiel

NB Dieses Beispiel ist wahrscheinlich zu einfach, um die Vorteile dieses Musters zu veranschaulichen, da es für einen so einfachen Fall ziemlich ausführlich ist.

/**
 * Container Component
 *
 *  - Manages component state
 *  - Does plumbing of data fetching/saving
 */

var PostEditorContainer = React.createClass({
  getInitialState: function() {
    return {
      text: ""
    };
  },

  componentWillMount: function() {
    this.setState({
      text: getPostText()
    });
  },

  updateText: function(text) {
    this.setState({
      text: text
    });
  },

  savePost: function() {
    savePostText(this.state.text);
  },

  render: function() {
    return (
      <PostEditor
        text={this.state.text}
        onChange={this.updateText.bind(this)}
        onSave={this.savePost.bind(this)}
      />
    );
  }
});


/**
 * Pure Display Component
 *
 *  - Calculates styling based on passed properties
 *  - Often just a render method
 *  - Uses methods passed in from container to announce changes
 */

var PostEditor = React.createClass({
  render: function() {
    return (
      <div>
        <input type="text" value={this.props.text} onChange={this.props.onChange} />
        <button type="button" onClick={this.props.onSave} />
      </div>
    );
  }
});

Leistungen

Indem Sie Anzeigelogik und Daten- / Statusverwaltung getrennt halten, haben Sie eine wiederverwendbare Anzeigekomponente, die:

  • kann leicht mit verschiedenen Requisitensätzen iteriert werden, indem so etwas wie ein Spielplatz für Reaktionskomponenten verwendet wird
  • kann für ein anderes Verhalten mit einem anderen Container umwickelt werden (oder mit anderen Komponenten kombiniert werden, um größere Teile Ihrer Anwendung zu erstellen

Sie haben auch eine Containerkomponente, die sich um die gesamte externe Kommunikation kümmert. Dies sollte es einfacher machen, flexibel auf den Zugriff auf Ihre Daten zuzugreifen, wenn Sie später ernsthafte Änderungen vornehmen *.

Dieses Muster erleichtert auch das Schreiben und Implementieren von Komponententests erheblich.

Nachdem ich einige Male eine große React-App wiederholt habe, habe ich festgestellt, dass dieses Muster die Dinge relativ schmerzlos macht, insbesondere wenn Sie größere Komponenten mit berechneten Stilen oder komplizierten DOM-Interaktionen haben.

* Informieren Sie sich über das Flussmuster und werfen Sie einen Blick auf Marty.js , das diese Antwort weitgehend inspiriert hat (und ich habe in letzter Zeit viel verwendet). Redux (und React-Redux ), das dieses Muster sehr gut implementiert.

Hinweis für diejenigen, die dies 2018 oder später lesen:

Die Reaktion hat sich seit dem Schreiben dieser Antwort ziemlich weiterentwickelt, insbesondere mit der Einführung von Hooks . Die zugrunde liegende Statusverwaltungslogik aus diesem Beispiel bleibt jedoch dieselbe, und was noch wichtiger ist, die Vorteile, die Sie durch die Trennung von Status und Präsentationslogik erhalten, gelten weiterhin auf dieselbe Weise.

Jim O'Brien
quelle
0

Ich denke, Sie verwenden ein Anti-Pattern, das Facebook bereits unter diesem Link erklärt hat

Folgendes finden Sie:

React.createClass({
  getInitialState: function() {
    return { value: { foo: 'bar' } };
  },

  onClick: function() {
    var value = this.state.value;
    value.foo += 'bar'; // ANTI-PATTERN!
    this.setState({ value: value });
  },

  render: function() {
    return (
      <div>
        <InnerComponent value={this.state.value} />
        <a onClick={this.onClick}>Click me</a>
      </div>
    );
  }
});

Wenn die innere Komponente zum ersten Mal gerendert wird, hat sie {foo: 'bar'} als Wertstütze. Wenn der Benutzer auf den Anker klickt, wird der Status der übergeordneten Komponente auf {value: {foo: 'barbar'}} aktualisiert, wodurch der erneute Rendervorgang der inneren Komponente ausgelöst wird, die {foo: 'barbar'} als empfängt der neue Wert für die Stütze.

Das Problem ist, dass sich die Requisite der inneren Komponente ändert, da die übergeordnete und die innere Komponente einen Verweis auf dasselbe Objekt verwenden, wenn das Objekt in Zeile 2 der onClick-Funktion mutiert wird. Wenn also der Prozess des erneuten Renderns startet und falls ComponentUpdate aufgerufen wird, ist this.props.value.foo gleich nextProps.value.foo, da this.props.value tatsächlich auf dasselbe Objekt wie nextProps.value verweist.

Infolgedessen wird die Benutzeroberfläche nicht von "bar" auf "barbar" aktualisiert, da wir die Änderung an der Requisite verpassen und den Rendering-Prozess kurzschließen.

Alex Nguyen
quelle
Können Sie bitte auch den InnercomponentsCode posten ?
Abdullah Khan