Lekcja #7: Wewnętrzny stan komponentu
Darmowy podglądKażdy komponent React będący klasą dziedziczącą z React.Component
może posiadać swój własny, wewnętrzny stan. Na stanie komponentu opierają się wszystkie interakcje z interfejsem użytkownika oraz wszystkie dynamiczne zmiany wyglądu danego komponentu.
Wiąże się to z cyklem życia komponentu, o którym więcej dowiesz się z kolejnej lekcji. Na tę chwilę musimy jedynie wiedzieć, że każda zmiana stanu komponentu powoduje ponowne wywołanie metody render
(oraz metod render
wszystkich komponentów dzieci, jeśli takie istnieją).
Deklaracja początkowego stanu komponentu
Deklaracji stanu komponentu oraz definicji jego początkowych wartości zawsze dokonujemy w konstruktorze klasy komponentu:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import React from 'react'; class HomeComponent extends React.Component { constructor(props) { super(props); this.state = { text: 'Janusz', age: 57 }; } // ... } |
Uwaga! Zwróć uwagę na pierwszą linię konstruktora czyli wywołanie
super(props)
– dobrą praktyką jest, aby zawsze wywoływać konstruktor bazowy przekazując mu obiektprops
! Wtedy, jeśli będziesz miał potrzebę odwołania się do tego obiektu, możesz zrobić to poprzezthis.props
, czyli tak jak w pozostałych miejscach w komponencie (jeśli tego nie zrobisz,this.props
zwróci wartość „undefined”). W przeciwnym razie jedynym sposobem dostępu do obiektuprops
w konstruktorze jest parametr wywołania konstruktora.
Wróćmy jednak do deklaracji stanu początkowego komponentu. Jak widać w przykładzie, aby zadeklarować stan komponentu wystarczy do właściwości this.state
przypisać obiekt. Wartości przypisane do poszczególnych właściwości tego obiektu stanowią stan początkowy komponentu.
Praca ze stanem komponentu
Stan komponentu wykorzystuje się w podobny sposób jak wartości przekazane do komponentu, które dostępne są poprzez obiekt props
. Mając stan komponentu, zdefiniowany tak jak w powyższym przykładzie, możemy łatwo wyświetlić jego wartości w metodzie render
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import React from 'react'; class HomeComponent extends React.Component { constructor(props) { super(props); this.state = { text: 'Janusz', age: 57 }; }; } render() { return ( <div> <h1>Imię: {this.state.text}</h1> <p><strong>Wiek:</strong> {this.state.age}</p> </div> ); } } |
Powyższy przykład implementacji metody render
pokazuje, że do poszczególnych wartości stanu można się dostać po prostu poprzez this.state
. Czyli tak jak już wspomniałem: analogicznie do this.props
.
Przekazywanie stanu do zagnieżdżonych komponentów
Jeśli dany komponent renderuje inne, zagnieżdżone komponenty, to możemy im przekazać stan komponentu rodzica. Robimy to za pomocą atrybutów, tak jak w każdym innym przypadku:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class HomeComponent extends React.Component { constructor(props) { super(props); this.state = { text: 'Janusz', age: 57 }; } render() { return ( <div> <UserData name={this.state.text} age={this.state.age} /> </div> ); } } |
W powyższym przykładzie do komponentu UserData
przekazujemy wartości stanu text
oraz age
przypisując je odpowiednio do atrybutów name
oraz age
komponentu. Wewnątrz komponentu UserData
dane te dostępne są już poprzez obiekt props
:
1 2 3 4 5 6 7 8 9 10 |
import React from 'react'; const UserData = (props) => { return ( <div> <h1>{props.text}</h1> <p><strong>age:</strong> {props.age}</p> </div> ); } |
Zmiana stanu komponentu
Do zmiany stanu komponentu służy specjalna metoda this.setState
dziedziczona z klasy React.Component
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class HomeComponent extends React.Component { constructor(props) { super(props); this.state = { counter: 0 }; } render() { return ( <div> <p>counter: {this.state.counter}</p> <button onClick={() => this.setState({ counter: this.state.counter + 1 })}> increment </button> </div> ); } } |
W przykładzie wykorzystano zdarzenie onClick
oraz funkcję „inline” obsługi zdarzenia – więcej na ten temat dowiesz się w dalszej części kursu. Na razie wystarczy jeśli napiszę, że powyższy kod wywołuje metodę this.setState
w momencie naciśnięcia guzika przez użytkownika. Skupmy się jednak na innej rzeczy – metodzie this.setState
, która powoduje zmianę stanu komponentu.
Jak widzisz, aby zmienić wartość stanu counter
, do funkcji this.setState
przekazano obiekt, zawierający właściwość o tej samej nazwie, do której przypisano nową wartość stanu (w przykładzie jest to poprzednia wartość zwiększona o jeden). Co ważne, jeśli w this.state
zdefiniowano więcej właściwości, nie oznacza to, że musimy wszystkie je powtarzać podczas wywołania metody this.setState
. Wystarczy przekazać obiekt zawierający tylko te właściwości, które faktycznie chcemy zmienić.
Uwaga! Każde wywołanie metody
this.setState
powoduje ponowne wywołanie metodyrender
komponentu (oraz metodrender
komponentów zagnieżdżonych). Jest to zachowanie pożądane, ponieważ pozwala odwzorować zmiany stanu na ekranie (więcej o tym w kolejnej lekcji). Z tego powodu, stan należy zmieniać tylko i wyłącznie za pomocą metodythis.setState
! Każda inna próba zmiany stanu, na przykład poprzez bezpośrednie przypisanie nowej wartości do właściwości stanu (na przykład:this.state.text = "hello"
) nie będzie skutkować ponownym renderowaniem (czyli nic się nie zmieni na widoku)!
Funkcja zmieniająca stan
Zanim przejdziemy dalej, warto wspomnieć o potencjalnych niebezpieczeństwach związanych z metodą this.setState
.
Z powodów wydajnościowych, operacja zmiany stanu może zostać przez React wykonana asynchronicznie i nie mamy na to wpływu. Z tego powodu nowa wartość stanu, która opiera się o aktualną jego wartość (tak, kod ostatniego przykładu nie jest idealny…), może to być potencjalnie niebezpieczne:
1 |
this.setState({ text: 'Hello ' + this.state.text }); |
W powyższym kodzie wartość this.state.text
może nie być jeszcze zaktualizowana poprzednią (albo być już zaktualizowana następną) zmianą, więc nie powinno się tego robić w powyższy sposób.
Jako że do komponentu, za pomocą atrybutów, może być przekazana wartość stanu innego komponentu, to tak samo nie należy przy zmianie stanu opierać się na aktualnych wartościach obiektu props
:
1 |
this.setState({ text: 'Hello ' + this.props.name }); |
Powyższy kod również jest potencjalnie niebezpieczny!
Na szczęście nie jesteśmy pozostawieni sami sobie z tym problemem! Metoda this.setState
, oprócz obiektu, może przyjmować również funkcję wywołania zwrotnego (ang. callback)… Spójrz na przykład:
1 2 3 |
this.setState((prevState, props) => { return { text: 'Hello ' + prevState.text }; }); |
W powyższym przykładzie skorzystaliśmy z parametru prevState
, który, jak sama nazwa wskazuje, zawiera (napewno) poprzedni stan komponentu. Jest więc bezpiecznie oprzeć się na nim przy definiowaniu nowego stanu.
Jak widzisz, funkcja ta przyjmuje również obiekt props
, możemy więc łatwo z niego skorzystać:
1 2 3 |
this.setState((prevState, props) => { return { text: 'Hello ' + props.name }; }); |
W ten sposób mamy pewność, że korzystamy z właściwej wartości obiektu props
.