Metody cyklu życia komponentu w React.js

Ten wpis jest 10 częścią z 16 w kursie React.js

Przy okazji omawiania komponentów będących klasami w React.js, wspomniałem też o możliwości korzystania z metod cyklu życia (lifecycle methods). Chciałbym do tego tematu teraz powrócić. Pokażę jakie metody cyklu życia definiuje React.js, do czego one służą i kiedy mogą się przydać.

Cykl życia komponentu

Każdy komponent ma pewien określony cykl życia. Na odpowiednich etapach wywoływane są też jego metody cyklu życia. Możemy je podzielić ogólnie na dwie grupy:

  • z nazwami zaczynającymi się od will — wywoływane zanim się coś wydarzy
  • z nazwami zaczynającymi się od did — wywoływane po tym jak coś się zdarzyło

Oto kolejne etapy życia komponentów:

Montowanie

Komponent jest tworzony i dodawany do drzewa Dom.

  • constructor(props) — jeśli definiujesz konstruktor to nie zapomnij wywołać w nim super(props) na samym początku; możesz też tam ustawić this.state = {…} bez konieczności używania setState. To będzie początkowy stan aplikacji. Inne operacje (efekty uboczne, subskrypcje) powinny się znaleźć w componentDidMount
  • componentWillMount() — wywoływany tuż przed zamontowaniem komponentu; ta metoda raczej Ci się nie przyda
  • render() — funkcja, która powinna zwrócić jeden z możliwych wyników:
    • element (JSX)
    • tablicę elementów (JSX)
    • string lub liczbę
    • null (nic się nie renderuje)
    • boolean (nic się nie renderuje)
    • Portal (stworzony przez ReactDOM.createPortal(…))
  • componentDidMount() — wywoływana po zamontowaniu komponentu; to dobre miejsce na jakiekolwiek funkcje polegające na DOM lub na subskrypcje (nie zapomnij o usunięciu subskrypcji w componentWillUnmount()!)

Aktualizacja

Update może zostać wywołany gdy zmieniają się props lub state.

  • componentWillReceiveProps(nextProps) — wywoływana m.in. gdy zmieniają się propsy (np. gdy element-rodzic je zmienia); warto porównać this.props z nextProps i sprawdzić czy rzeczywiście coś się zmieniło (bo nie zawsze musi…)
  • shouldComponentUpdate(nextProps, nextState) — wywoływana zawsze przed render(); jeśli z tej funkcji zwrócisz false to render() nie zostanie wykonany, a widok się raczej nie przerenderuje — można to wykorzystać do optymalizowania aplikacji; UWAGA: komponenty-dzieci nadal mogą się przerenderować np. gdy zmieni się ich state, a w przyszłości React będzie traktował funkcję  shouldComponentUpdate() tylko jako wskazówkę, a nie wyznacznik — wrócę do tego tematu jeszcze
  • componentWillUpdate(nextProps, nextState) — wywoływana tuż przed render() — pamiętaj aby nie modyfikować state wewnątrz tej funkcji
  • render() — j.w.
  • componentDidUpdate(prevProps, prevState) — wywoływana od razu po renderze; dobre miejsce na zmiany w DOM (jeśli takowe są potrzebne)

Odmontowanie

Wywoływane gdy komponent jest usuwany z DOM

  • componentWillUnmount() — wywoływana przed usunięciem komponentu z DOM; dobre miejsce na „posprzątanie” po sobie — usunięcie timerów, subskrypcji, zmian w DOM itd.

Łapanie błędów

  • componentDidCatch(error, info) — wywoływana gdy pojawi się błąd w czasie renderowania, wywoływania metod cyklu życia lub w konstruktorze — zagnieżdżonych komponentów; błędy w samym komponencie nie są tutaj łapane (zostaną złapane w komponencie-rodzicu)

Przykład

Oto prosta aplikacja, która pokazuje ważny przykład używania metod cyklu życia. Są to dwa komponenty, App odpowiada za pokazywanie i ukrywanie (po kliknięciu w przycisk) drugiego komponentu. Drugi komponent to Date, który co sekundę aktualizuje stan i wyświetla aktualną datę i godzinę. Wykorzystane zostały takie lifecycle methods:

  • constructor() — ustawianie początkowego stanu
  • componentDidMount() — rozpoczęcie odliczania (setInterval)
  • componentWillUnmount() — usunięcie odliczania (clearInterval)

Warto też zwrócić uwagę na sposób w jaki komponent App renderuje komponent Date w zależności od this.state.dateVisible:

<div>
  {this.state.dateVisible && <DateComponent />}
</div>

See the Pen Metody cyklu życia komponentu w React.js by Michał Miszczyszyn (@mmiszy) on CodePen.

Jeśli chcesz na bieżąco śledzić kolejne części kursu React.js to koniecznie polub mnie na Facebooku i zapisz się na newsletter.

Ćwiczenie

Ćwiczenie: Stwórz dwa komponenty (rodzic i dziecko). Oto wymagania:

  1. Rodzic pozwala na ustawienie (input + button + onClick+ setState) jakiejś wartości liczbowej i przekazuje ją do dziecka jako props.
  2. Dziecko ma początkowo wyświetlać liczbę podaną od rodzica, a dodatkowo ma umożliwiać zwiększanie i zmniejszanie tej liczby (button + onClick + setState).
  3. W momencie gdy rodzic ustawi liczbę, dziecko powinno zresetować swój stan do podanej liczby (componentWillReceiveProps).

Napisz w komentarzu czy się udało. A jeśli masz jakieś wątpliwości albo nawet nie wiesz jak zacząć — odezwij się! Pomogę!

  • Michał

    Poniżej rozwiązanie do ćwiczenia:

    https://pastebin.com/BtTwq70t


    class Parent extends React.Component {
    constructor(props) {
    super(props);

    this.state = {
    input: null,
    forwardValue: null
    }

    this.handleClick = this.handleClick.bind(this);
    this.handleChange = this.handleChange.bind(this);
    }

    handleChange(event) {
    this.setState({ input: event.target.value });
    }

    handleClick() {
    this.setState({ forwardValue: this.state.input });
    }

    render() {
    return(

    Propagate to child
    {this.state.forwardValue && }

    );
    }
    }

    class Child extends React.Component {
    constructor(props) {
    super(props);

    this.state = {
    fromParent: Number(props.value),
    current: Number(props.value),
    }

    this.handleIncrement = this.handleIncrement.bind(this);
    this.handleDecrement = this.handleDecrement.bind(this);
    }

    componentWillReceiveProps(nextProps) {
    if (nextProps.value != this.state.fromParent) {
    this.setState({
    fromParent: Number(nextProps.value),
    current: Number(nextProps.value),
    })
    }
    }

    handleIncrement() {
    this.setState({ current: this.state.current + 1 })
    }

    handleDecrement() {
    this.setState({ current: this.state.current - 1 })
    }

    render() {
    return(

    {this.state.current}
    +
    -

    );
    }
    }

    ReactDOM.render(
    ,
    document.getElementById('root')
    );

    • Świetne, czytelne rozwiązanie 🙂

      • Paulina

        Czy mogłabym poprosić o wytłumaczenie fragmentów kodu ? Staram się zrozumieć całość ale w tych miejscach mam problem, nie chcę iść dalej bo będę miała braki . Jestem początkująca i staram się zrozumieć podstawy.
        Pytanie : co się kryje pod „Number(props.value)” i dlaczego tak sie to pisze, oraz jesli chodzi o drugi fragment dlaczego akurat nextProps i skąd funkcja wie czym ona jest skoro nigdzie wczesniej tego nie ma. Poźniej rozumiem, iż porównuje sie wartości.
        this.state = {
        fromParent: Number(props.value),
        current: Number(props.value),
        }

        componentWillReceiveProps(nextProps) {
        if (nextProps.value != this.state.fromParent) {
        this.setState({
        fromParent: Number(nextProps.value),
        current: Number(nextProps.value),
        })
        }

        • Number to jedna z podstawowych funkcji w JS, służy do zamiany czegoś na liczbę. W tym przypadku propsy są stringami, więc autor chciał zmienić je na liczby.

          Co do componentWillReceiveProps — ta funkcja będzie wywoływana przez samego Reacta w momencie gdy zmienią się propsy. Zostanie do niej przekazany argument z nowymi propsami (zmienionymi), dzięki czemu można zareagować jakoś na zmiany. Nazwa argumentu nextProps pochodzi z dokumentacji i tak się przyjęło, ale oczywiście nie ma to żadnego znaczenia. Nazwy argumentów w funkcjach w JS mogą być zupełnie dowolne.

  • Kamil

    Cześć. Nie rozumiem za bardzo, dlaczego stosuje się constructor. Widziałem przykłady, gdzie ustawiano stan początkowy bez constructora. Od czego zależy jego użycie?

    A sam kurs bardzo fajny:)

    • Jeśli używasz klas to zawsze ustawia się początkowy state w konstruktorze. Mogłeś widzieć taki zapis:

      class Component extends React.Component {
      state = { … };
      }

      To jest składnia, która jeszcze nie jest częścią ECMAScript, jest tylko propozycją — ale działa z create-react-app (dzięki Babelowi). Oznacza to to samo co taki zapis:


      class Component extends React.Component {
      constructor(props) {
      super(props);
      state = { … };
      }
      }

      • Kamil

        No i wszystko jasne, dzięki:)