State anheben
These docs are old and won’t be updated. Go to react.dev for the new React docs.
These new documentation pages teach modern React and include live examples:
Häufig müssen mehrere Komponenten dieselben sich ändernden Daten widerspiegeln. Wir empfehlen, den gemeinsamen State bis zum nächsten gemeinsamen Vorfahren anzuheben. Schauen wir uns anhand eines Beispiels an, wie das funktioniert.
In diesem Abschnitt erstellen wir einen Temperaturrechner, der berechnet, ob das Wasser bei einer bestimmten Temperatur kocht.
Wir beginnen mit einer Komponente namens BoilingVerdict
. Sie akzeptiert die Temperatur in Celsius als Prop und gibt aus, ob diese ausreichend ist, um das Wasser zu kochen:
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>; }
return <p>The water would not boil.</p>;}
Als Nächstes erstellen wir eine Komponente namens Calculator
. Sie rendert ein <input>
, in das man die Temperatur eingeben kann, und speichert den Wert in this.state.temperature
.
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''}; }
handleChange(e) {
this.setState({temperature: e.target.value}); }
render() {
const temperature = this.state.temperature; return (
<fieldset>
<legend>Enter temperature in Celsius:</legend>
<input value={temperature} onChange={this.handleChange} /> <BoilingVerdict celsius={parseFloat(temperature)} /> </fieldset>
);
}
}
Ein zweites Eingabefeld hinzufügen
Unsere neue Anforderung ist es, zusätzlich zu einem Celsius-Eingabefeld, ein Fahrenheit-Eingabefeld bereitzustellen und beide synchron zu halten.
Wir können mit dem Extrahieren einer TemperatureInput
Komponente aus Calculator
beginnen. Wir werden ihr eine neue scale
Prop hinzufügen, die entweder “c” oder “f” sein kann:
const scaleNames = { c: 'Celsius', f: 'Fahrenheit'};
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
}
handleChange(e) {
this.setState({temperature: e.target.value});
}
render() {
const temperature = this.state.temperature;
const scale = this.props.scale; return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend> <input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
Wir können jetzt Calculator
anpassen, um zwei separate Temperatur-Eingabefelder zu rendern:
class Calculator extends React.Component {
render() {
return (
<div>
<TemperatureInput scale="c" /> <TemperatureInput scale="f" /> </div>
);
}
}
Wir haben jetzt zwei Eingabefelder. Aber wenn du die Temperatur in eines von ihnen eingibst, wird das andere nicht aktualisiert. Das widerspricht unserer Anforderung: wir wollen sie synchron halten.
BoilingVerdict
kann auch nicht von Calculator
aus angezeigt werden. Calculator
kennt die aktuelle Temperatur nicht, da sie in TemperatureInput
verborgen ist.
Konverterfunktionen schreiben
Zuerst schreiben wir zwei Funktionen, um von Celsius nach Fahrenheit und zurück zu konvertieren:
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
Diese beiden Funktionen konvertieren Zahlen. Wir schreiben eine weitere Funktion, die eine Zeichenfolge temperature
und eine Konverterfunktion als Argumente akzeptiert und eine Zeichenfolge zurückgibt. Wir werden sie verwenden, um den Wert eines Eingabefelds basierend auf dem anderen Eingabefeld zu berechnen.
Ist temperature
ungültig, gibt sie eine leere Zeichenfolge zurück. Außerdem rundet sie die Ausgabe auf die dritte Dezimalstelle:
function tryConvert(temperature, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
Beispielsweise gibt tryConvert('abc', toCelsius)
eine leere Zeichenfolge zurück, und tryConvert('10 .22 ', toFahrenheit)
gibt '50.396'
zurück.
State anheben
Derzeit speichern beide TemperatureInput
Komponenten ihre Werte unabhängig voneinaner im lokalen State:
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''}; }
handleChange(e) {
this.setState({temperature: e.target.value}); }
render() {
const temperature = this.state.temperature; // ...
Wir möchten jedoch, dass diese beiden Eingabefelder miteinander synchron sind. Wenn wir das Celsius-Eingabefeld aktualisieren, sollte das Fahrenheit-Eingabefeld die umgerechnete Temperatur widerspiegeln und umgekehrt.
In React ist gemeinsam genutzter State möglich, indem er auf den nächsten gemeinsamen Vorfahren der Komponenten verschoben wird, die ihn benötigen. Dies wird als “Anheben von State” bezeichnet. Wir werden den lokalen State aus TemperatureInput
entfernen und ihn stattdessen in die Calculator
Komponente verschieben.
Wenn Calculator
den gemeinsam genutzten State verwaltet, wird er in beiden Eingabefeldern zur “Quelle der Wahrheit” für die aktuelle Temperatur. Calculator
kann beide anweisen Werte zu haben, die miteinander übereinstimmen. Da die Props beider TemperatureInput
Komponenten von derselben übergeordneten Calculator
Komponente stammen, sind die beiden Eingabefelder immer synchron.
Schauen wir uns Schritt für Schritt an, wie das funktioniert.
Zuerst werden wir in der TemperatureInput
Komponente this.state.temperature
durch this.props.temperature
ersetzen. Für den Moment tun wir einfach so, als ob this.props.temperature
bereits vorhanden wäre, obwohl wir es erst noch von Calculator
aus übergeben müssen:
render() {
// Before: const temperature = this.state.temperature;
const temperature = this.props.temperature; // ...
Wir wissen, dass Props schreibgeschützt sind. Wenn sich temperature
im lokalen State befindet, kann TemperatureInput
einfach this.setState()
aufrufen, um sie zu ändern. Jetzt, da die Temperatur von der übergeordneten Komponente als Prop kommt, hat TemperatureInput
keine Kontrolle darüber.
In React wird das üblicherweise gelöst, indem man eine “kontrollierte Komponente” erzeugt. Genau wie das <input>
DOM-Element sowohl eine value
als auch eine onChange
Prop akzeptiert, kann auch die benutzerdefinierte TemperatureInput
Komponente sowohl die Temperatur als auch die onTemperatureChange
Prop der übergeordneten Calculator
Komponente akzeptieren.
Wenn nun TemperatureInput
seine Temperatur aktualisieren möchte, ruft sie this.props.onTemperatureChange
auf:
handleChange(e) {
// Before: this.setState({temperature: e.target.value});
this.props.onTemperatureChange(e.target.value); // ...
Hinweis:
Die Namen der Props
temperature
oderonTemperatureChange
haben keine besondere Bedeutung in benutzerdefinierten Komponenten. Wir hätten sie auch anders nennen können, z.B.value
undonChange
, was eine gängige Konvention ist.
Die onTemperatureChange
Prop wird zusammen mit der temperature
Prop von der übergeordneten Komponente Calculator
bereitgestellt. Die Änderung wird durch Modifizieren des eigenen lokalen State verarbeitet, sodass beide Eingabefelder mit den neuen Werten erneut gerendert werden. Wir werden uns sehr bald mit der neuen Calculator
Implementierung beschäftigen.
Bevor wir uns mit den Änderungen in Calculator
befassen, sollten wir uns nochmals die Änderungen an der TemperatureInput
Komponente anschauen. Wir haben den lokalen State entfernt, und anstatt this.state.temperature
auszulesen, greifen wir jetzt auf this.props.temperature
zu. Anstatt this.setState()
aufzurufen, wenn wir eine Änderung vornehmen möchten, rufen wir jetzt this.props.onTemperatureChange()
auf, die von Calculator
bereitgestellt wird:
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onTemperatureChange(e.target.value); }
render() {
const temperature = this.props.temperature; const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
Wenden wir uns nun der Calculator
Komponente zu.
Wir speichern temperature
und scale
des aktuellen Eingabefelds in seinem lokalen State. Dies ist der State, den wir von den Eingabefeldern “angehoben” haben, und er wird für beide als “Quelle der Wahrheit” dienen. Es ist die minimale Darstellung aller Daten, die wir benötigen, um beide Eingabefelder zu rendern.
Wenn wir zum Beispiel 37 in das Celsius-Eingabefeld eingeben, ist der State der Calculator
Komponente:
{
temperature: '37',
scale: 'c'
}
Wenn wir in das Fahrenheit-Feld später 212 eingeben, ist der State von Calculator
:
{
temperature: '212',
scale: 'f'
}
Wir hätten den Wert beider Eingabefelder speichern können, das erweist sich jedoch als unnötig. Es reicht aus, den Wert des zuletzt geänderten Eingabefelds und den von ihre verwendete Maßeinheit zu speichern. Wir können dann den Wert des anderen Eingabefelds allein auf Grundlage der aktuellen Werte von temperature
und scale
ableiten.
Die Eingabefelder bleiben synchron, da ihre Werte aus demselben State berechnet werden:
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {temperature: '', scale: 'c'}; }
handleCelsiusChange(temperature) {
this.setState({scale: 'c', temperature}); }
handleFahrenheitChange(temperature) {
this.setState({scale: 'f', temperature}); }
render() {
const scale = this.state.scale; const temperature = this.state.temperature; const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature; const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
return (
<div>
<TemperatureInput
scale="c"
temperature={celsius} onTemperatureChange={this.handleCelsiusChange} /> <TemperatureInput
scale="f"
temperature={fahrenheit} onTemperatureChange={this.handleFahrenheitChange} /> <BoilingVerdict
celsius={parseFloat(celsius)} /> </div>
);
}
}
Unabhängig davon, welches Eingabefeld du bearbeitest, this.state.temperature
und this.state.scale
werden jetzt in Calculator
aktualisiert. Bei einem der Eingabefelder wird der Wert unverändert übernommen, sodass alle Benutzereingaben erhalten bleiben und der andere Eingabewert immer auf dieser Basis neu berechnet wird.
- React ruft die als
onChange
angegebene Funktion im DOM-Element<input>
auf. In unserem Fall ist dies diehandleChange
Methode in derTemperatureInput
Komponente. - Die
handleChange
Methode derTemperatureInput
Komponente ruftthis.props.onTemperatureChange()
mit dem neuen gewünschten Wert auf. Ihre Props, einschließlichonTemperatureChange
, wurden von der übergeordnetenCalculator
Komponente bereitgestellt. - Als die
Calculator
Komponente zuletzt gerendert wurde, hat sie festgelegt, dassonTemperatureChange
der CelsiusTemperatureInput
Komponente ihrehandleCelsiusChange
Methode ist, undonTemperatureChange
der FahrenheitTemperatureInput
Komponente ihrehandleFahrenheitChange
Methode. Eine dieser beiden Methoden vonCalculator
wird also aufgerufen, je nachdem welche Eingabe wir bearbeitet haben. - Innerhalb dieser Methoden fordert die
Calculator
Komponente React auf sich selbst erneut zu rendern, indemthis.setState()
mit dem neuen Eingabewert und der aktuellen Maßeinheit des gerade bearbeiteten Eingabefelds aufgerufen wird. - React ruft die Render-Methode der
Calculator
Komponente auf, um zu erfahren, wie die Benutzeroberfläche aussehen soll. Die Werte beider Eingabefelder werden basierend auf der aktuellen Temperatur und des aktiven Maßstabs neu berechnet. Die Temperaturkonvertierung wird an dieser Stelle durchgeführt. - React ruft die Render-Methoden der einzelnen
TemperatureInput
Komponenten mit ihren neuen, vonCalculcator
festgelegten Props auf. Es erfährt so, wie ihre Benutzeroberfläche aussehen soll. - React ruft die Render-Methode der
BoilingVerdict
-Komponente auf und übergibt die Temperatur in Celsius als Prop. - React DOM aktualisiert das DOM mit dem kochenden Ergebnis und den gewünschten Eingabewerten. Das gerade bearbeitete Eingabefeld erhält seinen aktuellen Wert und das andere Eingabefeld wird nach der Umrechnung auf die entsprechende Temperatur aktualisiert.
Bei jedem Update werden dieselben Schritte durchlaufen, sodass die Eingabefelder synchron bleiben.
Gewonnene Erkenntnisse
Es sollte eine einzige “Quelle der Wahrheit” für alle Daten geben, die sich in einer React-Anwendung ändern. Normalerweise wird der State zuerst der Komponente hinzugefügt, die ihn zum Rendern benötigt. Wenn andere Komponenten ihn ebenfalls benötigen, kannst du ihn bis zu ihrem nächsten gemeinsamen Vorfahren anheben. Anstatt zu versuchen den State zwischen verschiedenen Komponenten zu synchronisieren, solltest du dich auf den Top-Down-Datenfluss verlassen.
Beim Anheben des State muss mehr Code geschrieben werden als bei einer so genannten “bidirektionalen Bindung” (two-way binding). Der Vorteil ist jedoch, dass weniger Arbeit erfoderlich ist, um Fehler zu finden und zu isolieren. Da jeder State in einer Komponente “lebt” und nur diese Komponente ihn ändern kann, ist der Spielraum für Fehler stark eingeschränkt. Darüber hinaus kannst du jegliche benutzerdefinierte Logik implementieren, um Benutzereingaben abzulehnen oder umzuwandeln.
Wenn etwas aus den Props oder dem State abgeleitet werden kann, sollte es wahrscheinlich nicht im State sein. Anstatt celsiusValue
und fahrenheitValue
zu speichern, speichern wir nur die zuletzt bearbeitete Temperatur und deren Maßeinheit. Der Wert der anderen Eingabefelder kann in der Methode render()
immer daraus berechnet werden. Auf diese Weise können wir das andere Feld löschen oder Rundungen anwenden, ohne dass dabei die Genauigkeit der Benutzereingaben verloren geht.
Wenn in der Benutzeroberfläche etwas nicht stimmt, kannst du die Props mithilfe der React Developer Tools überprüfen und den Komponenten-Baum nach oben durchgehen, bis du die für die Aktualisierung des State verantwortliche Komponente findest. So kannst du Fehler bis zu ihrer Quelle zurückverfolgen: