Wie greife ich in React auf den Status des Kindes zu?

214

Ich habe folgende Struktur:

FormEditor- enthält mehrere FieldEditor FieldEditor- bearbeitet ein Feld des Formulars und speichert verschiedene Werte darüber in seinem Status

Wenn in FormEditor auf eine Schaltfläche geklickt wird, möchte ich in der Lage sein, Informationen zu den Feldern aller FieldEditorKomponenten zu sammeln , Informationen, die sich in ihrem Status befinden, und alles in FormEditor zu haben.

Ich habe überlegt, die Informationen zu den Feldern außerhalb des FieldEditorStatus zu speichern und sie FormEditorstattdessen in den Status zu versetzen . Dies würde jedoch erfordern, dass FormEditorjede ihrer FieldEditorKomponenten abgehört wird, wenn sie ihre Informationen ändern und in ihrem Zustand speichern.

Kann ich nicht stattdessen einfach auf den Kinderstaat zugreifen? Ist es ideal

Moshe Revah
quelle
4
"Kann ich nicht stattdessen einfach auf den Kinderstaat zugreifen? Ist es ideal?" Nein. Der Zustand ist etwas Inneres und sollte nicht nach außen lecken. Sie könnten Accessor-Methoden für Ihre Komponente implementieren, aber selbst das ist nicht ideal.
Felix Kling
@FelixKling Dann schlagen Sie vor, dass der ideale Weg für die Kommunikation zwischen Kind und Eltern nur Ereignisse sind?
Moshe Revah
3
Ja, Ereignisse sind eine Möglichkeit. Oder haben Sie einen Datenfluss in eine Richtung, wie ihn Flux fördert: facebook.github.io/flux
Felix Kling
1
Wenn Sie FieldEditors nicht separat verwenden, FormEditorklingt es gut , den Status in zu speichern . In diesem Fall werden Ihre FieldEditorInstanzen basierend auf der propsÜbergabe durch ihren Formulareditor und nicht anhand ihres Formulars gerendert state. Eine komplexere, aber flexiblere Möglichkeit wäre, einen Serializer zu erstellen, der alle FormEditoruntergeordneten Container durchläuft, alle Instanzen unter ihnen findet und sie in ein JSON-Objekt serialisiert. Das JSON-Objekt kann optional verschachtelt werden (mehr als eine Ebene), basierend auf den Verschachtelungsebenen der Instanzen im Formulareditor.
Meglio
4
Ich denke, der React Docs 'Lifting State Up' ist wahrscheinlich der 'Reacty'-Weg, dies zu tun
icc97

Antworten:

135

Wenn Sie bereits einen onChange-Handler für die einzelnen FieldEditors haben, verstehe ich nicht, warum Sie den Status nicht einfach in die FormEditor-Komponente verschieben und von dort aus einen Rückruf an die FieldEditors weiterleiten konnten, die den übergeordneten Status aktualisieren. Das scheint mir eine reaktionsfreudigere Art zu sein.

Etwas in dieser Richtung vielleicht:

const FieldEditor = ({ value, onChange, id }) => {
  const handleChange = event => {
    const text = event.target.value;
    onChange(id, text);
  };

  return (
    <div className="field-editor">
      <input onChange={handleChange} value={value} />
    </div>
  );
};

const FormEditor = props => {
  const [values, setValues] = useState({});
  const handleFieldChange = (fieldId, value) => {
    setValues({ ...values, [fieldId]: value });
  };

  const fields = props.fields.map(field => (
    <FieldEditor
      key={field}
      id={field}
      onChange={handleFieldChange}
      value={values[field]}
    />
  ));

  return (
    <div>
      {fields}
      <pre>{JSON.stringify(values, null, 2)}</pre>
    </div>
  );
};

// To add abillity to dynamically add/remove fields keep the list in state
const App = () => {
  const fields = ["field1", "field2", "anotherField"];

  return <FormEditor fields={fields} />;
};

Original - Pre-Hooks-Version:

Markus-ipse
quelle
2
Dies ist nützlich für onChange, aber nicht so sehr für onSubmit.
190290000 Ruble Man
1
@ 190290000RubleMan Ich bin mir nicht sicher, was Sie meinen, aber da die onChange-Handler in den einzelnen Feldern sicherstellen, dass in Ihrer FormEditor-Komponente immer die vollständigen Formulardaten in Ihrem Status verfügbar sind, würde Ihr onSubmit nur so etwas enthalten myAPI.SaveStuff(this.state);. Wenn Sie etwas näher darauf eingehen, kann ich Ihnen vielleicht eine bessere Antwort geben :)
Markus-ipse
Angenommen, ich habe ein Formular mit 3 Feldern unterschiedlichen Typs, von denen jedes seine eigene Validierung hat. Ich möchte jeden von ihnen vor dem Absenden validieren. Wenn die Validierung fehlschlägt, sollte jedes fehlerhafte Feld erneut gerendert werden. Ich habe nicht herausgefunden, wie ich das mit Ihrer vorgeschlagenen Lösung machen soll, aber vielleicht gibt es einen Weg!
190290000 Ruble Man
1
@ 190290000RubleMan Scheint ein anderer Fall zu sein als die ursprüngliche Frage. Öffnen Sie eine neue Frage mit mehr Details und vielleicht etwas Beispielcode, und ich kann später einen Blick darauf werfen :)
Markus-ipse
Vielleicht finden Sie diese Antwort nützlich : Sie nutzt den Haken, die es abdeckt , in denen der Staat erstellt werden soll, wie bei jedem Tastendruck Form wieder machen zu vermeiden, wie die Feldüberprüfung, etc. zu tun
Andrew
189

Bevor ich näher darauf eingehe, wie Sie auf den Status einer untergeordneten Komponente zugreifen können, lesen Sie bitte die Antwort von Markus-ipse bezüglich einer besseren Lösung für dieses spezielle Szenario.

Wenn Sie tatsächlich auf den Status der untergeordneten Elemente einer Komponente zugreifen möchten, können Sie refjedem untergeordneten Element eine Eigenschaft zuweisen . Es gibt jetzt zwei Möglichkeiten, Referenzen zu implementieren: Verwenden React.createRef()und Rückrufen von Refs.

Verwenden von React.createRef()

Dies ist derzeit die empfohlene Methode zur Verwendung von Referenzen ab React 16.3 ( weitere Informationen finden Sie in den Dokumenten ). Wenn Sie eine frühere Version verwenden, finden Sie unten Informationen zu Rückrufreferenzen.

Sie müssen im Konstruktor Ihrer übergeordneten Komponente eine neue Referenz erstellen und diese dann über das refAttribut einem untergeordneten Element zuweisen .

class FormEditor extends React.Component {
  constructor(props) {
    super(props);
    this.FieldEditor1 = React.createRef();
  }
  render() {
    return <FieldEditor ref={this.FieldEditor1} />;
  }
}

Um auf diese Art von Referenz zuzugreifen, müssen Sie Folgendes verwenden:

const currentFieldEditor1 = this.FieldEditor1.current;

Dadurch wird eine Instanz der bereitgestellten Komponente zurückgegeben, sodass Sie currentFieldEditor1.stateauf den Status zugreifen können.

Nur eine kurze Anmerkung, um zu sagen, dass, wenn Sie diese Referenzen auf einem DOM-Knoten anstelle einer Komponente (z. B. <div ref={this.divRef} />) verwenden this.divRef.current, das zugrunde liegende DOM-Element anstelle einer Komponenteninstanz zurückgegeben wird.

Rückruf Refs

Diese Eigenschaft übernimmt eine Rückruffunktion, der ein Verweis auf die angehängte Komponente übergeben wird. Dieser Rückruf wird unmittelbar nach dem Ein- oder Aushängen der Komponente ausgeführt.

Beispielsweise:

<FieldEditor
    ref={(fieldEditor1) => {this.fieldEditor1 = fieldEditor1;}
    {...props}
/>

In diesen Beispielen wird die Referenz in der übergeordneten Komponente gespeichert. Um diese Komponente in Ihrem Code aufzurufen, können Sie Folgendes verwenden:

this.fieldEditor1

und dann verwenden this.fieldEditor1.state, um den Zustand zu erhalten.

Beachten Sie, dass Ihre untergeordnete Komponente gerendert wurde, bevor Sie versuchen, darauf zuzugreifen ^ _ ^

Wenn Sie diese Referenzen wie oben auf einem DOM-Knoten anstelle einer Komponente (z. B. <div ref={(divRef) => {this.myDiv = divRef;}} />) verwenden, this.divRefwird das zugrunde liegende DOM-Element anstelle einer Komponenteninstanz zurückgegeben.

Weitere Informationen

Wenn Sie mehr über die Ref-Eigenschaft von React erfahren möchten, besuchen Sie diese Seite von Facebook.

Stellen Sie sicher , dass Sie den Abschnitt " Refs nicht überbeanspruchen " lesen, in dem steht, dass Sie die des Kindes nicht verwenden sollten, stateum "Dinge geschehen zu lassen".

Hoffe das hilft ^ _ ^

Bearbeiten: React.createRef()Methode zum Erstellen von Refs hinzugefügt . ES5-Code entfernt.

Martoid Prime
quelle
5
Auch ordentlicher kleiner Leckerbissen, von dem aus man Funktionen aufrufen kann this.refs.yourComponentNameHere. Ich habe es nützlich gefunden, um den Status über Funktionen zu ändern. Beispiel: this.refs.textInputField.clearInput();
Dylan Pierce
7
Achtung (aus den Dokumenten): Refs sind eine großartige Möglichkeit, eine Nachricht an eine bestimmte untergeordnete Instanz auf eine Weise zu senden, die über das Streaming von Reactive propsund unpraktisch wäre state. Sie sollten jedoch nicht Ihre bevorzugte Abstraktion für den Datenfluss durch Ihre Anwendung sein. Verwenden Sie standardmäßig den reaktiven Datenfluss und speichern Sie refs für Anwendungsfälle, die von Natur aus nicht reaktiv sind.
Lynn
2
Ich bekomme nur den Status, der im Konstruktor definiert wurde (nicht den aktuellen Status)
Vlado Pandžić
3
Die Verwendung von Refs wird jetzt als " Nicht kontrollierte Komponenten " klassifiziert , mit der Empfehlung, "Kontrollierte Komponenten" zu verwenden
icc97
Ist dies möglich, wenn die untergeordnete Komponente eine Redux- connectedKomponente ist?
Jmancherje
6

Es ist 2020 und viele von Ihnen werden hierher kommen, um nach einer ähnlichen Lösung zu suchen, aber mit Hooks (sie sind großartig!) Und mit den neuesten Ansätzen in Bezug auf Code-Sauberkeit und Syntax.

Wie in früheren Antworten angegeben, besteht der beste Ansatz für diese Art von Problem darin, den Zustand außerhalb der untergeordneten Komponente zu halten fieldEditor. Sie können dies auf verschiedene Arten tun.

Am "komplexesten" ist der globale Kontext (Status), auf den sowohl Eltern als auch Kinder zugreifen und den sie ändern können. Es ist eine großartige Lösung, wenn Komponenten sehr tief in der Baumhierarchie verankert sind und es daher kostspielig ist, Requisiten in jeder Ebene zu senden.

In diesem Fall denke ich, dass es sich nicht lohnt, und ein einfacherer Ansatz bringt uns die gewünschten Ergebnisse, wenn wir nur die Mächtigen verwenden React.useState().

Vorgehensweise mit dem Hook React.useState (), viel einfacher als mit Klassenkomponenten

Wie gesagt, wir werden uns mit Änderungen befassen und die Daten unserer untergeordneten Komponente fieldEditorin unserem übergeordneten Element speichern fieldForm. Dazu senden wir einen Verweis auf die Funktion, die die Änderungen behandelt und auf den fieldFormStatus anwendet. Sie können dies tun mit:

function FieldForm({ fields }) {
  const [fieldsValues, setFieldsValues] = React.useState({});
  const handleChange = (event, fieldId) => {
    let newFields = { ...fieldsValues };
    newFields[fieldId] = event.target.value;

    setFieldsValues(newFields);
  };

  return (
    <div>
      {fields.map(field => (
        <FieldEditor
          key={field}
          id={field}
          handleChange={handleChange}
          value={fieldsValues[field]}
        />
      ))}
      <div>{JSON.stringify(fieldsValues)}</div>
    </div>
  );
}

Beachten Sie, dass React.useState({})ein Array zurückgegeben wird, wobei Position 0 der beim Aufruf angegebene Wert ist (in diesem Fall leeres Objekt) und Position 1 die Referenz auf die Funktion ist, die den Wert ändert.

Mit der untergeordneten Komponente müssen FieldEditorSie nicht einmal eine Funktion mit einer return-Anweisung erstellen, eine Lean-Konstante mit einer Pfeilfunktion reicht aus!

const FieldEditor = ({ id, value, handleChange }) => (
  <div className="field-editor">
    <input onChange={event => handleChange(event, id)} value={value} />
  </div>
);

Aaaaund wir sind fertig, nichts weiter, mit nur diesen beiden Schleimfunktionskomponenten haben wir unser Endziel, auf unseren Kinderwert "zuzugreifen" FieldEditorund ihn in unseren Eltern zu zeigen.

Sie können die akzeptierte Antwort von vor 5 Jahren überprüfen und sehen, wie Hooks den React-Code schlanker gemacht haben (um einiges!).

Ich hoffe, meine Antwort hilft Ihnen dabei, mehr über Hooks zu lernen und zu verstehen. Wenn Sie hier ein funktionierendes Beispiel überprüfen möchten, ist dies der Fall .

Josep Vidal
quelle
4

Jetzt können Sie auf den InputField-Status zugreifen, der das untergeordnete Element von FormEditor ist.

Grundsätzlich erhalten wir bei jeder Änderung des Status des Eingabefelds (untergeordnet) den Wert vom Ereignisobjekt und übergeben diesen Wert an das übergeordnete Element, in dem der Status im übergeordneten Element festgelegt ist.

Beim Klicken auf die Schaltfläche wird nur der Status der Eingabefelder gedruckt.

Der entscheidende Punkt hierbei ist, dass wir die Requisiten verwenden, um die ID / den Wert des Eingabefelds abzurufen und auch die Funktionen aufzurufen, die als Attribute für das Eingabefeld festgelegt sind, während wir die wiederverwendbaren untergeordneten Eingabefelder generieren.

class InputField extends React.Component{
  handleChange = (event)=> {
    const val = event.target.value;
    this.props.onChange(this.props.id , val);
  }

  render() {
    return(
      <div>
        <input type="text" onChange={this.handleChange} value={this.props.value}/>
        <br/><br/>
      </div>
    );
  }
}       


class FormEditorParent extends React.Component {
  state = {};
  handleFieldChange = (inputFieldId , inputFieldValue) => {
    this.setState({[inputFieldId]:inputFieldValue});
  }
  //on Button click simply get the state of the input field
  handleClick = ()=>{
    console.log(JSON.stringify(this.state));
  }

  render() {
    const fields = this.props.fields.map(field => (
      <InputField
        key={field}
        id={field}
        onChange={this.handleFieldChange}
        value={this.state[field]}
      />
    ));

    return (
      <div>
        <div>
          <button onClick={this.handleClick}>Click Me</button>
        </div>
        <div>
          {fields}
        </div>
      </div>
    );
  }
}

const App = () => {
  const fields = ["field1", "field2", "anotherField"];
  return <FormEditorParent fields={fields} />;
};

ReactDOM.render(<App/>, mountNode);
Dhana
quelle
7
Ich bin mir nicht sicher, ob ich dies unterstützen kann. Was erwähnen Sie hier, das noch nicht von @ Markus-ipse beantwortet wurde?
Alexander Bird
3

Versuchen Sie, wie in den vorherigen Antworten angegeben, den Status in eine oberste Komponente zu verschieben und den Status durch Rückrufe zu ändern, die an die untergeordneten Komponenten übergeben werden.

Für den Fall , dass Sie für den Zugriff auf ein Kind Zustand wirklich brauchen , die als funktionale Komponente (Haken) deklariert werden Sie erklären kann ref in der übergeordneten Komponente, es dann als Pass ref Attribut für das Kind , aber Sie müssen verwenden React.forwardRef und dann den Hook useImperativeHandle , um eine Funktion zu deklarieren, die Sie in der übergeordneten Komponente aufrufen können.

Schauen Sie sich das folgende Beispiel an:

const Parent = () => {
    const myRef = useRef();
    return <Child ref={myRef} />;
}

const Child = React.forwardRef((props, ref) => {
    const [myState, setMyState] = useState('This is my state!');
    useImperativeHandle(ref, () => ({getMyState: () => {return myState}}), [myState]);
})

Dann sollten Sie in der Lage sein, myState in der übergeordneten Komponente abzurufen, indem Sie Folgendes aufrufen: myRef.current.getMyState();

Mauricio Avendaño
quelle