Wie erzwinge ich die erneute Montage von React-Komponenten?

102

Nehmen wir an, ich habe eine Ansichtskomponente mit einem bedingten Rendering:

render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

MyInput sieht ungefähr so ​​aus:

class MyInput extends React.Component {

    ...

    render(){
        return (
            <div>
                <input name={this.props.name} 
                    ref="input" 
                    type="text" 
                    value={this.props.value || null}
                    onBlur={this.handleBlur.bind(this)}
                    onChange={this.handleTyping.bind(this)} />
            </div>
        );
    }
}

Sagen wir, employedist wahr. Immer wenn ich es auf false umschalte und die andere Ansicht gerendert wird, wird nur unemployment-durationneu initialisiert. Auch unemployment-reasonwird mit dem Wert aus vorbelegt job-title(wenn ein Wert gegeben wurde , bevor der Zustand geändert).

Wenn ich das Markup in der zweiten Renderroutine in etwa so ändere:

render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <span>Diff me!</span>
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

Es scheint, als ob alles gut funktioniert. Es sieht so aus, als würde React die Berufsbezeichnung und den Grund für die Arbeitslosigkeit nicht unterscheiden.

Bitte sag mir, was ich falsch mache ...

o01
quelle
1
Was ist data-reactidauf jedem der MyInput(oder input, wie in DOM zu sehen) Elemente vor und nach dem employedWechsel?
Chris
@Chris Ich konnte nicht angeben, dass die MyInput-Komponente die in a eingeschlossene Eingabe rendert <div>. Die data-reactidAttribute scheinen sowohl im Wrapping-Div als auch im Eingabefeld unterschiedlich zu sein. job-titleEingabe erhält ID data-reactid=".0.1.1.0.1.0.1", während unemployment-reasonEingabe ID erhältdata-reactid=".0.1.1.0.1.2.1"
o01
1
und was ist mit unemployment-duration?
Chris
@ Chris Sorry, ich habe zu früh gesprochen. Im ersten Beispiel (ohne die "diff me" -Spanne) sind die reactidAttribute identisch job-titleund unemployment-reasonim zweiten Beispiel (mit der diff span) unterschiedlich.
o01
@ Chris für unemployment-durationdas reactidAttribut ist immer eindeutig.
o01

Antworten:

107

Was wahrscheinlich passiert, ist, dass React denkt, dass nur eins MyInput( unemployment-duration) zwischen den Renderings hinzugefügt wird. Als solches wird das job-titlenie durch das ersetzt unemployment-reason, weshalb auch die vordefinierten Werte vertauscht werden.

Wenn React den Diff ausführt, wird anhand ihrer keyEigenschaft bestimmt, welche Komponenten neu und welche alt sind . Wenn der Code keinen solchen Schlüssel enthält, generiert er einen eigenen.

Der Grund, warum das letzte von Ihnen bereitgestellte Code-Snippet funktioniert, ist, dass React im Wesentlichen die Hierarchie aller Elemente unter dem übergeordneten Element ändern muss, divund ich glaube, dass dies ein erneutes Rendern aller untergeordneten Elemente auslösen würde (weshalb es funktioniert). Hätten Sie spandas unten statt oben hinzugefügt , würde sich die Hierarchie der vorhergehenden Elemente nicht ändern und diese Elemente würden nicht neu gerendert (und das Problem würde bestehen bleiben).

In der offiziellen React-Dokumentation heißt es:

Die Situation wird komplizierter, wenn die Kinder herumgemischt werden (wie in den Suchergebnissen) oder wenn neue Komponenten am Anfang der Liste hinzugefügt werden (wie in Streams). In diesen Fällen, in denen die Identität und der Status jedes Kindes über Renderpässe hinweg beibehalten werden müssen, können Sie jedes Kind eindeutig identifizieren, indem Sie ihm einen Schlüssel zuweisen.

Wenn React die verschlüsselten Kinder abgleichen, wird sichergestellt, dass jedes Kind mit Schlüssel neu angeordnet (statt überladen) oder zerstört (statt wiederverwendet) wird.

Sie sollten dies beheben können, indem keySie dem übergeordneten Element divoder allen MyInputElementen selbst ein eindeutiges Element bereitstellen .

Beispielsweise:

render(){
    if (this.state.employed) {
        return (
            <div key="employed">
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div key="notEmployed">
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

ODER

render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput key="title" ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <MyInput key="reason" ref="unemployment-reason" name="unemployment-reason" />
                <MyInput key="duration" ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

Wenn React nun den Diff ausführt, wird es feststellen, dass die divsUnterschiede unterschiedlich sind, und es wird einschließlich aller seiner untergeordneten Elemente neu gerendert (1. Beispiel). Im zweiten Beispiel wird der Diff ein Erfolg sein job-titleund unemployment-reasonda sie jetzt unterschiedliche Schlüssel haben.

Sie können natürlich alle gewünschten Schlüssel verwenden, sofern diese eindeutig sind.


Update August 2017

Um einen besseren Einblick in die Funktionsweise von Schlüsseln in React zu erhalten, empfehle ich dringend, meine Antwort auf Grundlegendes zu eindeutigen Schlüsseln in React.js zu lesen .


Update November 2017

Dieses Update sollte vor einiger Zeit veröffentlicht worden sein, aber die Verwendung von String-Literalen in refist jetzt veraltet. Zum Beispiel ref="job-title"sollte jetzt stattdessen sein ref={(el) => this.jobTitleRef = el}(zum Beispiel). Weitere Informationen finden Sie in meiner Antwort auf die Warnung vor Verfall mit this.refs .

Chris
quelle
10
it will determine which components are new and which are old based on their key property.- das war ... Schlüssel ... zu meinem Verständnis
Larry
1
Vielen Dank ! Ich hatte auch herausgefunden, dass alle untergeordneten Komponenten einer Karte erfolgreich neu gerendert wurden und ich konnte nicht verstehen, warum.
ValentinVoilean
Ein netter Hinweis ist, eine Zustandsvariable (die sich ändert, wie z. B. eine ID) als Schlüssel für das div zu verwenden!
Macilias
200

Ändern Sie den Schlüssel der Komponente.

<Component key="1" />
<Component key="2" />

Die Komponente wird nicht gemountet und eine neue Instanz der Komponente wird gemountet, da sich der Schlüssel geändert hat.

Bearbeiten : Dokumentiert über Sie benötigen wahrscheinlich keinen abgeleiteten Status :

Wenn sich ein Schlüssel ändert, erstellt React eine neue Komponenteninstanz, anstatt die aktuelle zu aktualisieren. Schlüssel werden normalerweise für dynamische Listen verwendet, sind aber auch hier nützlich.

Alex K.
quelle
45
Dies sollte in der React-Dokumentation viel offensichtlicher sein. Dies erreichte genau das, wonach ich suchte, aber es dauerte Stunden, um es zu finden.
Paul
4
Das hat mir sehr geholfen! Vielen Dank! Ich musste eine CSS-Animation zurücksetzen, aber der einzige Weg, dies zu tun, besteht darin, ein erneutes Rendern zu erzwingen. Ich bin damit einverstanden, dass dies in der Reaktionsdokumentation offensichtlicher sein sollte
Alex. P.
@ Alex.P. Nett! CSS-Animationen können manchmal schwierig sein. Es würde mich interessieren, ein Beispiel zu sehen.
Alex K
1
React Dokumentation, die diese Technik beschreibt: reactjs.org/blog/2018/06/07/…
GameSalutes
Danke dir. Ich bin so dankbar dafür. Ich habe versucht, nicht deklarierten Requisiten wie "randomNumber" Zufallszahlen zuzuführen, aber die einzige Requisite, die funktionierte, war der Schlüssel.
ShameWare
5

Verwenden Sie setStatein Ihrer Ansicht, um die employedEigenschaft des Status zu ändern . Dies ist ein Beispiel für die React-Render-Engine.

 someFunctionWhichChangeParamEmployed(isEmployed) {
      this.setState({
          employed: isEmployed
      });
 }

 getInitialState() {
      return {
          employed: true
      }
 },

 render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <span>Diff me!</span>
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}
Dmitriy
quelle
Ich mache das schon. Die Variable 'used' ist Teil des Status der Ansichtskomponenten und wird über die Funktion setState geändert. Tut mir leid, dass ich das nicht erklärt habe.
o01
'beschäftigte' Variable sollte Eigenschaft des Reaktionszustands sein. In Ihrem Codebeispiel ist dies nicht offensichtlich.
Dmitriy
Wie ändern Sie this.state.employed? Zeigen Sie bitte den Code. Sind Sie sicher, dass Sie setState an der richtigen Stelle in Ihrem Code verwenden?
Dmitriy
Die Ansichtskomponente ist etwas komplexer als in meinem Beispiel gezeigt. Zusätzlich zu den MyInput-Komponenten wird eine Radiobutton-Gruppe gerendert, die den Wert von 'Employed' mit einem onChange-Handler steuert. Im onChange-Handler habe ich den Status der Ansichtskomponente entsprechend festgelegt. Die Ansicht wird wieder gut gerendert, wenn sich der Wert der Radiobutton-Gruppe ändert, aber React glaubt, dass die erste MyInput-Komponente dieselbe ist, wie sie zuvor geändert wurde.
o01
0

Ich arbeite an Crud für meine App. So habe ich es gemacht Got Reactstrap als meine Abhängigkeit.

import React, { useState, setState } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import firebase from 'firebase';
// import { LifeCrud } from '../CRUD/Crud';
import { Row, Card, Col, Button } from 'reactstrap';
import InsuranceActionInput from '../CRUD/InsuranceActionInput';

const LifeActionCreate = () => {
  let [newLifeActionLabel, setNewLifeActionLabel] = React.useState();

  const onCreate = e => {
    const db = firebase.firestore();

    db.collection('actions').add({
      label: newLifeActionLabel
    });
    alert('New Life Insurance Added');
    setNewLifeActionLabel('');
  };

  return (
    <Card style={{ padding: '15px' }}>
      <form onSubmit={onCreate}>
        <label>Name</label>
        <input
          value={newLifeActionLabel}
          onChange={e => {
            setNewLifeActionLabel(e.target.value);
          }}
          placeholder={'Name'}
        />

        <Button onClick={onCreate}>Create</Button>
      </form>
    </Card>
  );
};

Einige React Hooks drin

Ruben Verster
quelle
Willkommen bei SO! Vielleicht möchten Sie überprüfen, wie Sie eine gute Antwort schreiben . Fügen Sie eine Erklärung hinzu, wie Sie die Frage gelöst haben, ohne nur den Code zu veröffentlichen.
Vertriebene