Sporo już napisałem na blogu na temat Reacta… Wciąż jednak nie poruszyłem bardzo istotnej sprawy jaką jest cykl życia komponentu React. Nawet chyba któryś z czytelników prosił mnie o poruszenie tego tematu w prywatnej wiadomości… Dlatego też tym właśnie zagadnieniem zajmiemy się dzisiaj!

Myślę, że nie ma co tego wstępu przedłużać. Wpis ten jest rozszerzeniem wiedzy jaką przekazałem w poście na temat podstaw React - napisałem tam m.in. o tym czym jest komponent React, do czego służy metoda render() itp. Dziś przedstawię więcej szczegółów na temat pozostałych metod komponentu React.

Cykl życia komponentu React

Ogólnie rzecz biorąc, klasa React.Component, z której dziedziczymy nasze własne komponenty posiada kilka metod wywoływanych w różnych momentach jego cyklu życia. Metody te możemy oczywiście przeciążać w naszych komponentach. Jedną z takich metod jest wspomniana już metoda render(), którą musi przeciążać każdy komponent. Przeciążanie pozostałych nie jest wymagane i możemy z nich korzystać tylko wtedy gdy tego potrzebujemy.

Generalnie, cykl życia komponentu React możemy podzielić na trzy etapy: montowanie, odświeżanie oraz demontowanie. Poniżej przedstawiam cały cykl życia komponentu React (a właściwie to nazwy metod tego cyklu) w podziale na poszczególne grupy:

  • Montowanie (ang. “mount”) - poniższe metody wywoływane są kiedy komponent jest tworzony i dodawany do drzewa DOM:
    • constructor()
    • componentWillMount()
    • render()
    • componentDidMount()
  • Odświeżanie (ang. “update”) - poniższe metody wywoływane są w przypadkuzmiany stanu komponentu lub zmiennych zapisanych w obiekcie this.props:
    • componentWillReceiveProps()
    • shouldComponentUpdate()
    • componentWillUpdate()
    • render()
    • componentDidUpdate()
  • Demontowanie (ang. “unmount”) - metoda wywoływana kiedy komponent jest usuwany z drzewa DOM:
    • componentWillUnmount()

Na pewno od razu rzuciło Ci się w oczy, że metoda render() jest wywoływana zarówno podczas tworzenia jak i odświeżania komponentu. To dlatego, że odpowiada ona za wygląd komponentu, który musi być pokazany podczas pierwszego wyświetlenia komponentu jak i musi odzwierciedlić na ekranie każdą zmianę stanu komponentu.

Poniżej przedstawiam każdy z etapów cyklu życia komponentu React wraz z przykładami wywołań poszczególnych metod!

Etap montowania komponentu

Myślę, że metody cyklu życia komponentu React wywoływane na etapie montowania komponentu można fajnie przedstawić na przykładzie. Spójrz więc na poniższy komponent React:

import React from 'react';
import service from './service';

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: []
    };
  }

  componentWillMount() {
    console.log('this is called before render!');
  }

  componentDidMount() {
    this.service.getData().then(response => {
      this.setState({
        data: response.data
      })
    });
  }

  render() {
    return (
      <div>
        {this.state.data.map((item, index) => {
          return <div key={index}>{item.name}</div>;
        })}
      </div>
    );
  }
}

export default MyComponent;

Objaśnienie przykładu

Pierwszą metodą, która wywołana zostanie w powyższym komponencie jest oczywiście konstruktor. Tak jak pisałem już we wpisie na temat podstaw React, jest to miejsce, w którym należy inicjować stan komponentu. W przykładzie stan ten zawiera jedną właściwość - data, którą inicjujemy jako pustą tablicę.

Kolejną metodą jaka zostanie wywołana podczas fazy montowania komponentu MyComponent jest componentWillMount(). Jak widzisz, umieściłem tam tylko logowanie do konsoli. Nie jest to bowiem dobre miejsce ani na wywoływanie metody this.setState() ani na wywoływanie żądań AJAX. Wszystko dlatego, że metoda ta wywoływana jest przed wywołaniem metody render(). Z tego względu aktualizacja stanu na tym etapie nie spowoduje re-renderowania, a co za tym idzie, na ekranie zobaczymy stan początkowy, a nie ten zaktualizowany. Ogólnie metodę tę wykorzystuje się raczej nie często.

poszczególne etapy cyklu życia komponentu React

Następna w kolejce jest metoda render(). Myślę, że na jej temat już sporo zostało powiedziane w moim poście o podstawach ReactJS. W przykładzie widać iterację, za pomocą metody Array.map(), po elementach tablicy this.state.data. Pamiętaj jednak, że na tym etapie jest to jeszcze pusta tablica - podczas etapu montowania, do renderowania wykorzystuje się stan początkowy, który ustawiliśmy w konstruktorze. Dopiero później, na etapie odświeżania metoda ta zostanie wywołana ponownie… ale o tym za chwilę.

Zaraz po pierwszym wyrenderowaniu komponentu na ekranie, wywoływana jest ostatnia metoda tego etapu - componentDidMount(). W przykładzie widać, że wywołuję w niej metodę getData() jakiegoś hipotetycznego serwisu o nazwie service. Na podstawie użycia metody then() domyślasz się pewnie, że jest to wywołanie AJAX, a metoda getData() zwraca “promise”. W odpowiedzi na ten “promise”, aktualizowany jest stan komponentu (za pomocą metody this.setState()).

Metoda componentDidMount() jest najodpowiedniejszym miejscem na aktualizację stanu komponentu czy to bezpośrednio czy w wyniku wywołania AJAX. Jeśli używasz Reduxa, to właśnie w tej metodzie powinieneś wywołać akcję, które aktualizuje początkowy stan za pomocą żądań do API. Aktualizacja stanu w tej metodzie, jak i w innych, będących na przykład reakcją na interakcję z użytkownikiem, powoduje rozpoczęcie etapu aktualizacji komponentu. Więcej na ten temat poniżej.

Etap odświeżania komponentu

Aby wyjaśnić cykl życia komponentu React na etapie odświeżania, zmodyfikuję co nieco nasz przykładowy komponent:

import React from 'react';
import service from './service';

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: []
    };
  }

  componentWillMount() {
    console.log('this is called before first render!');
  }

  componentDidMount() {
    this.service.getData().then(response => {
      this.setState({
        data: response.data
      })
    });
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.reset) {
      this.setState({ data: [] });
    }
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log('returning false will prevent re-rendering!');
    // return false;
  }

  componentWillUpdate(nextProps, nextState) {
    console.log('this is called before re-render!');
  }

  componentDidUpdate(prevProps, prevState) {
    if (this.props.getDataAgain) {
      this.service.getData().then(response => {
        this.setState({
          data: response.data
        })
      });
    }
  }

  render() {
    return (
      <div>
        {this.state.data.map((item, index) => {
          return <div key={index}>{item.name}</div>;
        })}
      </div>
    );
  }
}

export default MyComponent;

Objaśnienie przykładu

Do przykładu z etapu montowania dodałem kilka nowych metod odpowiedzialnych za obsługę etapu odświeżania. Poniżej kilka słów na temat każdej z metod.

Pierwszą metodą wywoływaną na etapie odświeżania komponentu jest componentWillReceiveProps(). Do metody tej, jako parametr przekazywany jest obiekt zawierający nowe “propsy”. Możemy więc wykorzystać go do sprawdzenia, czy odświeżanie nastąpiło w wyniku zmiany obiektu this.props (obiekt this.props nie jest jeszcze na tym etapie zaktualizowany). Jest to też miejsce, w którym dozwolone jest wywoływanie metody this.setState(). W przykładzie sprawdzam czy nowe “propsy” zawierają właściwość reset ustawioną na true i jeśli tak, “czyszczę” stan ustawiając wartość właściwości data z powrotem na pustą tablicę.

Kolejna metoda cyklu życia komponentu React wywoływana na tym etapie to shouldComponentUpdate(). Standardowo, komponent React jest re-renderowany przy każdej zmianie stanu lub obiektu this.props. Jednak za pomocą tej metody można tym sterować! Wystarczy, że zwróci ona wartość false i wszystkie kolejne metody cyklu życia zostaną anulowane. Aby pomóc nam w zdecydowaniu czy anulować re-renderowanie, metoda ta przyjmuje dwa parametry: nextProps oraz nextState. Zawierają one odpowiednio: nowy obiekt this.props oraz nowy stan komponentu. Można więc wykorzystać je na przykład, do porównania ich z ich aktualnymi wartościami itp.

etap odświeżania komponentu

Idźmy dalej - następna w kolejce jest metoda componentWillUpdate(), która ma podobne przeznaczenie jak wspomniana wcześniej metoda componentWillMount(). Jest ona wywoływana zaraz przed metodą render() i nie powinna zawierać wywołania this.setState() ponieważ, nie spowoduje to kolejnego re-renderowania.

Teraz przyszedł czas na wywołanie metody render(). Na tym etapie stan jest już zaktualizowany, więc w naszym przykładzie tablica this.state.data nie jest już pusta. Z tego powodu na ekranie wyświetlą się w tym momencie dane pobrane z serwera.

Na koniec etapy odświeżania wywoływana jest metoda componentDidUpdate(), która jest odpowiednikiem metody componentDidMount() znanej z etapu montowania. Przyjmuje ona parametry: prevProps oraz prevState, które zawierają poprzednią wartość obiektu this.props oraz stanu komponentu. Jest to również miejsce, w którym możemy aktualizować stan komponentu itp. W przykładzie, w przypadku gdy właściwość getDataAgain obiektu this.props jest ustawiona na true, wykonuję aktualizację danych zapisanych w stanie komponentu.

Etap demontażu komponentu

Oprócz dwóch wyżej omówionych, cykl życia komponentu React posiada jeszcze etap demontażu komponentu. Dla obsługi tego etapu mamy dostępną do przeciążenia tylko jedną metodę: componentWillUnmount(). Myślę, że nie ma sensu pokazywać jej na przykładzie. Wystarczy wiedzieć, że nie przyjmuje ona żadnych argumentów i służy do wykonywania ewentualnych zadań czyszczenia.

Jeśli więc na przykład wykorzystujesz Reduxa z RxJS to możesz tutaj wywołać anulowanie żądań do serwera. Jeśli w metodzie componentDidMount() dodałeś jakieś dodatkowe, dynamicznie utworzone elementy DOM, to tutaj możesz je wyczyścić… itp. itd. Ogólnie ze swojego doświadczenia z Reactem mogę powiedzieć, że metody tej nie wykorzystuje się aż tak często jak niektóre z wcześniej wymienionych (co oczywiste najcześciej używa się metody render(), a componentDidMount() jest na drugim miejscu).

Podsumowanie

To tyle jeśli chodzi o cykl życia komponentu React. Myślę, że warto znać wszystkie omówione dziś metody oraz kolejność w jakiej są one wywoływane. Dodatkowo, wydaje mi się, że dają one dobry obraz tego jak działa React - jak stan komponentu jest powiązany z tym co wyświetlane jest na ekranie i jaki wpływ ma na to zmiana tego stanu. Mam nadzieję, że udało mi się w dość przystępny sposób to wszystko opisać. W razie pytań zapraszam do komentowania pod artykułem. Jestem też jak zawsze otwarty na wiadomości prywatne wysyłane przez formularz kontaktowy!