Úvod do konceptu stat a lifecycle komponet v Reactu.
Použijeme příklad z předchozích lekcí, tikajících se tikajících hodin, ve kterém jsme se naučili pouze jeden způsob, jak aktualizovat uživatelské rozhraní, pomocí ReactDOM.render()
pro vykreslovaní výstupu:
GitHub
function tick() { | |
const element = ( | |
<div> | |
<h1>Hello, world!</h1> | |
<h2>It is {new Date().toLocaleTimeString()}.</h2> | |
</div> | |
); | |
ReactDOM.render(element, document.getElementById('root')); | |
} | |
setInterval(tick, 1000); |
V této části se dozvíme, jak vyrobit komponentu Clock
, která bude opakovaně použitelná a zapouzdřená. Nastaví vlastní časovač a aktualizuje se každou sekundu.
Začneme zapouzdřením, jak hodiny vypadají:
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
);
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
}
setInterval(tick, 1000);
Nicméně chybí zásadní požadavek: skutečnost, že Clock
nastaví časovač a aktualizuje uživatelské rozhraní každou sekundu, by měl být záležitostí implementace Clock
.
V ideálním případě bychom to chtěli jednou napsat a mít vlastní Clock
aktualizaci:
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
K tomu je třeba přidat do komponenty Clock
"state".
State je podobný props, ale je soukromý a plně kontrolovaný komponentou.
Již dříve jsme zmínili, že komponenty definované jako třídy mají některé další funkce. Lokální state je přesně: funkce dostupná pouze pro třídy.
Převedení funkce na třídu
Funkci komponenty lze převést jako třídu Clock
v pěti krocích:
- Vytvořte třídu ES6 se stejným názvem, která rozšiřuje třídu
React.Component
. - Přidejte do ní jednu prázdnou metodu
render()
. - Přesuňte tělo funkce do
render()
metody. - Změňte
props
zathis.props
do tělarender()
. - Vymažte prázdnou deklaraci funkce.
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
Clock
je nyní definována spíše jako třída než jako funkce.
Metoda render
bude volána pokaždé, když nastane aktualizace, ale pokud budeme renderovat < Clock / >
do stejného DOM uzlu bude použita pouze jediná instance třídy Clock
. To umožňuje používat další funkce, jako jsou lokální state a metody životního cyklu.
Přidání Local State a Class
Přesuneme date
z props do state ve třech krocích:
1. Změňte this.props.date
za this.state.date
v metodě render()
:
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
2. Přidejte konstruktor pro inicializaci this.state
:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
Všimněte si jak props přistupuje k základnímu konstruktoru:
constructor(props) {
super(props);
this.state = {date: new Date()};
}
Třída komponenty by měla vždy volat základní konstruktor pomocí props
.
3. Vymažte date
prop z elementu < Clock / >
:
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
Později přidáme kód časovače zpět k samotné komponentě,
výsledek vypadá následovně:
GitHub
class Clock extends React.Component { | |
constructor(props) { | |
super(props); | |
this.state = {date: new Date()}; | |
} | |
render() { | |
return ( | |
<div> | |
<h1>Hello, world!</h1> | |
<h2>It is {this.state.date.toLocaleTimeString()}.</h2> | |
</div> | |
); | |
} | |
} | |
ReactDOM.render( | |
<Clock />, | |
document.getElementById('root') | |
); |
Následně vytvoříme Clock
pro nastavení aktuálního času a jeho automatické změny.
Přidání Lifecycle metod a třídy
V aplikacích s mnoha komponentami je velmi důležité uvolnit zdroje komponent, které jsou zničeny.
Chceme nastavit časovač, kdykoli Clock
poprvé vykreslíme do DOM. Toto se nazývá v Reactu "mouting".
Také bychom chtěli vymazat tento časovač, kdykoli se DOM Clock
vymaže. Toto se v reakci nazývá "unmounting".
Můžeme deklarovat speciální metody na třídě komponent, aby při spuštění a odpojování součásti spustili nějaký kód:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
}
componentWillUnmount() {
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
Tyto metody se nazývají "lifecycle metody".
Metoda componentDidMount() se vol8 á po vykreslení DOM, tedy po výstupu komponenty. Takže to je to správné místo pro nastavení časovače:
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
Všimněte si, že dimerID je uloženo s this
.
Zatímco je this.props
v Reactu nastaven sám sobě, this.state
má zvláštní význam. Můžete volně přidat další pole do třídy,jestliže potřebujete uložit něco co se netýká toku dat.
Zrušení časovače se provádí pomocí metody componentWillUnmount()
:
omponentWillUnmount() {
clearInterval(this.timerID);
}
Nakonec implementujeme metodu tick()
, pro aktualizace Clock
každou sekundu.
Použije se this.setState()
k naplánování aktualizací lokálního state komponent:
GitHub
class Clock extends React.Component { | |
constructor(props) { | |
super(props); | |
this.state = {date: new Date()}; | |
} | |
componentDidMount() { | |
this.timerID = setInterval( | |
() => this.tick(), | |
1000 | |
); | |
} | |
componentWillUnmount() { | |
clearInterval(this.timerID); | |
} | |
tick() { | |
this.setState({ | |
date: new Date() | |
}); | |
} | |
render() { | |
return ( | |
<div> | |
<h1>Hello, world!</h1> | |
<h2>It is {this.state.date.toLocaleTimeString()}.</h2> | |
</div> | |
); | |
} | |
} | |
ReactDOM.render( | |
<Clock />, | |
document.getElementById('root') | |
); |
Nyní se hodiny mění každou sekundu.
Rychlá rekapitulace toho co se děje v jakých metodách, které jsou volány:
- Při odeslání
doReactDOM.render()
reaguje konstruktor komponentyClock
. ProtožeClock
potřebuje zobrazit aktuální čas, inicializujethis.state
s objektem včetně aktuálního času. Tento stav později aktualizujeme. - React pak volá metodu komponety
Clock
render()
. Takto se React dozví, co se má zobrazit na obrazovce. React poté aktualizuje DOM tak, aby odpovídala výstupuClock
. - Když je výstup
Clock
vložen do DOM, React zavolá metoducomponentDidMount()
životního cyklu. Uvnitř komponentyClock
se žádá prohlížeč aby nastavil časovač, který volá metodu komponentytick()
jednou za sekundu. - Každou sekundu prohlížeč volá metodu
tick()
. Uvnitř komponentyClock
naplánuje aktualizaci uživatelského rozhraní volánímsetState()
s objektem obsahujícím aktuální čas. DíkysetState()
React ví, že stav se změnil arender()
znovu zavolá metodu, aby zjistil, co má být na obrazovce. Tentokrát sethis.state.date
vrender()
metodě bude lišit a tak výstup renderu bude obsahovat aktualizovaný čas. Služba React aktualizuje DOM odpovídajícím způsobem. - Pokud je součást
Clock
někdy odebrána z DOM, React zavolácomponentWillUnmount()
metodu životního cyklu tak, aby byl časovač zastaven.
Jak správně používat state
Existují tři věci, které bystě měli při práci se setState()
vědět.
Nemodifikujte přímo state
Například nerenderujte komponentu:
// Wrong
this.state.comment = 'Hello';
Použijte radši setState()
:
// Correct
this.setState({comment: 'Hello'});
Jediným místem, kde můžete použít this.state
je konstruktor.
Aktualizace state mohou být asynchronní
React může volat setState()
vícekrát pro jednu aktualizaci.
Protože this.props
a this.state
mohou být aktualizovány asynchronně, něměli bychom spoléhat na jejich hodnoty pro výpočet dalšího stavu.
Například tento kód nemusí aktualizovat počítadlo:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
Pokud vyše uvedený kód chceme upravit, použijeme druhou podobu setState()
, která přijímá funkci dříve než objekt. Tato funkce obdrží předchozí stav jako první argument a props v době použití jako druhý argument:
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
Použili jsme funkci šipky, ale pracujeme také s běžnými funkcemi:
// Correct
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});
State aktualizace jsou slučovány
Když voláme setState()
, React sloučí objekty do aktuálního stavu.
Například state může obsahovat několik nezávislých proměnných:
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
Potom je lze aktualizovat nezávisle, pomocí volání setState()
.
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
Průchod z hora dolů
Ani rodiče, ani podřízené komponenty nemohou vědět, zda je určitá součást state nebo bez state, a neměli by se starat o to, zda je definována jako funkce nebo třída.
To je důvod, proč se state často nazývá lokální nebo zapouzdřený. Není přístupná žádné jiné komponentě, než té, která jej vlastní a nastavuje.
Komponenta se může rozhodnout, že svůj state předá dolů jako props do svých podřízených komponent:
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
To také funguje pro uživatelem definované komponenty:
<FormattedDate date={this.state.date} />
Komponenta FormattedDate
by obdržela date
ve svých props a nebude vědět, zda to přišlo z Clock
‚ se state, z Clock
s props, nebo bylo napsáno ručně:
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
To se běžně nazývá tok dat "shora dolů" nebo "jednosměrný". Každý state je vždy vlastněn určitou konkrétní komponentou a všechna data nebo uživatelské rozhraní odvozené od tohoto state mohou ovlivňovat pouze komponenty "pod" ve stromu.
Pokud si představíte strom komponent jako vodopád props, každý state komponenty je jako další zdroj vody, který se připojí k libovolnému bodu, ale také klesá.
Abychom ukázali, že všechny komponenty jsou skutečně izolované, můžeme vytvořit komponentu App
, která vykreslí tři <Clock>
:
GitHub
function FormattedDate(props) { | |
return <h2>It is {props.date.toLocaleTimeString()}.</h2>; | |
} | |
class Clock extends React.Component { | |
constructor(props) { | |
super(props); | |
this.state = {date: new Date()}; | |
} | |
componentDidMount() { | |
this.timerID = setInterval( | |
() => this.tick(), | |
1000 | |
); | |
} | |
componentWillUnmount() { | |
clearInterval(this.timerID); | |
} | |
tick() { | |
this.setState({ | |
date: new Date() | |
}); | |
} | |
render() { | |
return ( | |
<div> | |
<h1>Hello, world!</h1> | |
<FormattedDate date={this.state.date} /> | |
</div> | |
); | |
} | |
} | |
function App() { | |
return ( | |
<div> | |
<Clock /> | |
<Clock /> | |
<Clock /> | |
</div> | |
); | |
} | |
ReactDOM.render(<App />, document.getElementById('root')); |
Každý Clock
nastaví vlastní časovač a aktualizuje se nezávisle.
V aplikacích React, zda je komponenta se state nebo bez state, se považuje za podrobnosti implementaci součásti, která se může časem měnit. Ve state komponentách můžete použít komponentu bez state a naopak.