Často je třeba, aby některé komponenty předávali stejné měnící se údaje. Doporučujeme zvednout sdílený stav až na nejbližšího společného předka. Podívejme se, jak to funguje v akci.
V této části vytvoříme kalkulačku teploty, která vypočítá, zda by se voda měla při určité teplotě vařit.
Začneme s komponentou nazvanou BoilingVerdict
. Přijme teplotu v celsius
jako podklad pro to test, zda se bude vařit voda:
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
Dále vytvoříme komponentu nazvanou Calculator
. Ukazuje element, < input >
který umožňuje zadat teplotu a udržuje její hodnotu this.state.temperature
.
Navíc vykresluje BoilingVerdict
aktuální vstupní hodnotu.
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>
);
}
}
GitHub
Přidání druhého vstupu
Naším novým požadavkem je, že vedle pro zadání Celsia poskytujeme pole pro zadání Fahrenheita a jsou udržovány v synchronizaci.
Můžeme začít extrahováním TemperatureInput
komponenty z Calculator
. Přidáme novou scale
rekvizitu k tomu, že může být buď "c"
anebo "f"
:
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>
);
}
}
Nyní můžeme změnit, Calculator
abychom měli dva oddělené teplotní vstupy:
class Calculator extends React.Component {
render() {
return (
<div>
<TemperatureInput scale="c" />
<TemperatureInput scale="f" />
</div>
);
}
}
Nyní máme dva vstupy, ale když zadáte teplotu v jednom z nich, druhý se neaktualizuje. To je v rozporu s naším požadavkem: chceme je udržovat v synchronizaci.
Nemůžeme také zobrazit BoilingVerdict
z Calculator
. Calculator
nezná aktuální teplotu, protože se skrývá uvnitř TemperatureInput
.
Napsání konverzních funkcí
Nejprve napíšeme dvě funkce, které převádí z Celsia na Fahrenheita a zpět:
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
Tyto dvě funkce převádějí čísla. Budeme psát další funkci, která vezme řetězec temperature
a konvertuje ji jako argument a vrátí řetězec. Použijeme jej k výpočtu hodnoty jednoho vstupu na základě druhého vstupu.
Vrátí prázdný řetězec na neplatné temperature
a udržuje výstup zaokrouhlený na třetí desetinné místo:
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();
}
Například tryConvert('abc', toCelsius)
vrátí prázdný řetězec a tryConvert('10.22', toFahrenheit)
vrátí se '50.396'
.
Sdílení stavu
V současné době obě složky TemperatureInput
nezávisle udržují své hodnoty v lokálním stavu:
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;
// ...
Nicméně chceme, aby byly tyto dva vstupy vzájemně synchronizovány. Když aktualizujeme vstup Celsia, vstup Fahrenheita by měl odrážet převedenou teplotu a naopak.
V Reactu je sdílení stavu dosaženo přesunutím až k nejbližšímu společnému předku komponent, které jej potřebují. Toto se nazývá "sdílení stavu". Odstraníme z něj lokální stav TemperatureInput
a Calculator
místo toho jej přesměrujeme .
Pokud Calculator
vlastní sdílený stav, stává se "zdrojem pravdy" pro současnou teplotu v obou vstupech. Může jim nařídit, aby měli hodnoty, které jsou vzájemně konzistentní. Vzhledem k tomu, že props obou TemperatureInput
komponent pocházejí ze stejné rodičovské Calculator
komponenty, budou dva vstupy vždy synchronizovány.
Uvidíme, jak to funguje krok za krokem.
Nejprve this.state.temperature
se this.props.temperature
v TemperatureInput
komponentě nahradíme. Prozatím budeme předstírat, že this.props.temperature
již existuje, ačkoli to budeme muset předat v budoucnu Calculator
:
render() {
// Before: const temperature = this.state.temperature;
const temperature = this.props.temperature;
// ...
Víme, že props jsou jen pro čtení. Když temperature
byl v lokálním stavu, TemperatureInput
mohl jen volat this.setState()
o změnu. Nicméně, nyní, když temperature
pochází od rodiče jako podpora, TemperatureInput
nemá nad ním kontrolu.
V Reactu se to obvykle řeší tím, že se součást "řídí". Stejně jako DOM přijímá oba
value
a onChange
prop, tak může uživatel TemperatureInput
přiznat oba temperature
a onTemperatureChange
props od svého rodiče Calculator
.
Nyní, když TemperatureInput
chce aktualizovat teplotu, volá this.props.onTemperatureChange
:
handleChange(e) {
// Before: this.setState({temperature: e.target.value});
this.props.onTemperatureChange(e.target.value);
// ...
onTemperatureChange
props bude dodána s temperature
stojkou mateřskou Calculator
komponentou. To zvládne změnu tím, že upraví svůj vlastní lokální stav, čímž znovu provede oba vstupy s novými hodnotami. V Calculator
se podíváme na novou implementaci.
Předtím, než se ponoříme do změn Calculator
, zopakujme naše změny komponenty TemperatureInput
. Odstranili jsme z něj místní stav a namísto čtení this.state.temperature
jsme četli this.props.temperature
. Namísto volání, this.setState()
když chceme provést změnu, nyní voláme this.props.onTemperatureChange()
, kterou poskytnou Calculator
:
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>
);
}
}
Teď se podíváme na komponentu Calculator
.
Uložíme aktuální vstupy temperature
a scale
jejich lokální stav. To je stav, který jsme zvedli ze vstupů, a bude sloužit jako "zdroj pravdy" pro oba. Je to minimální reprezentace všech dat, které potřebujeme znát, abychom získali oba vstupy.
Například pokud zadáme hodnotu 37 do stupně Celsia, stav Calculator
komponenty bude:
{
temperature: '37',
scale: 'c'
}
Pokud budeme později upravovat pole Fahrenheita na 212, stav Calculator
bude:
{
temperature: '212',
scale: 'f'
}
Mohli jsme uložit hodnotu obou vstupů, ale bylo to zbytečné. Stačí uložit hodnotu naposledy změněného vstupu a veličiny, kterou reprezentuje. Pak můžeme odvodit hodnotu druhého vstupu založeného na aktuálním temperature
a scale
samotném.
Vstupy zůstávají synchronizovány, protože jejich hodnoty jsou vypočítávány ze stejného stavu:
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>
);
}
}
Nyní, bez ohledu na to, který vstup jste upravili, this.state.temperature
a this.state.scale
v Calculator
aktualizovat. Jeden ze vstupů dostane hodnotu tak, jak je, takže každý uživatelský vstup je zachován a druhá vstupní hodnota je vždy na základě toho přepočítána.
Zjistěte, co se stane, když upravíte vstup:
- React volá funkci zadané jako
onChange
v DOM. V našem případě jde ohandleChange
metodu vTemperatureInput
komponentě. - Metodu
handleChange
v komponentěTemperatureInput
voláthis.props.onTemperatureChange()
s novou požadovanou hodnotu. Jeho props, včetněonTemperatureChange
, byly poskytnuty jeho mateřská složka, tj.Calculator
. - Když se již poskytnuté se
Calculator
určil, žeonTemperatureChange
v stupních CelsiaTemperatureInput
jeCalculator
tohandleCelsiusChange
způsob, aonTemperatureChange
v FahrenheitTemperatureInput
jeCalculator
tohandleFahrenheitChange
metoda. Každá z těchto dvouCalculator
metod se tedy volá podle toho, který vstup jsme upravili. - Uvnitř těchto metod
Calculator
požádá komponenta React, aby se znovu vykreslil volánímthis.setState()
s novou vstupní hodnotou a aktuálním měřítkem vstupu, který jsme právě upravili. - React volá metodu
Calculator
komponenty arender
zjistí, jak by měl vypadat uživatelský rozhraní. Hodnoty obou vstupů se přepočítávají na základě aktuální teploty a aktivní stupnice. Konverze teploty se provádí zde. - React volá
render
metody jednotlivýchTemperatureInput
komponent s novými props, které jsou specifikovány příkazemCalculator
. Učí se, jak by měl vypadat jejich uživatelský rozhraní. - React volá
render
metoduBoilingVerdict
komponenty a prochází teplotu ve stupních Celsia. - React DOM aktualizuje DOM s větším verdiktem a odpovídá požadovaným vstupním hodnotám. Vstup, který jsme právě upravili, obdrží jeho aktuální hodnotu a druhý vstup je po konverzi aktualizován na teplotu.
Každá aktualizace prochází stejnými kroky, takže vstupy zůstávají v synchronizaci.
GitHub