Moduł 3 - Zajęcia 5 - Cykl Życiowy

Cykl życiowy

Istnieje kilka stadiów cyklu życia komponentu - montowanie, aktualizacja i odmontowywanie. W czasie każdego z nich w komponencie-klasie wywoływane są metody odziedziczone od React.Component. Możemy przedefiniować ich zachowanie, dodając niezbędną funkcjonalność w ramach określonych zasad.

Istnieje siedem metod cyklu życiowego, pomijając render i constructor. W praktyce, do większości codziennych zadań wykorzystywane są trzy: componentDidMount, componentDidUpdate i componentWillUnmount.

Link do oryginalnej strony_diagramu

Etap montowania

Następujące metody wywoływane są po kolei, gdy komponent jest tworzony i dodawany do drzewa DOM.

constructor

            class Alert extends Component {
                constructor() {
                    super()
                    console.log('Alert constructor!')
                }
            }
          

Wywoływany jest w momencie utworzenia egzemplarza komponentu, zanim zostanie on umieszczony w DOM.

  • Inicjalizuje stan początkowy komponentu.
  • Przywiązuje kontekst (this) w metodach.
  • Nie można w nim wywoływać setState().
  • W większości przypadków jawne zadeklarowanie konstruktora nie jest potrzebne.
  • Należy pamiętać o dodaniu super() ****przed**** wywołaniem jakiekolwiek kodu w konstruktorze - w przeciwnym wypadku otrzymany błąd: Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

getDerivedStateFromProps

            static getDerivedStateFromProps(nextProps, prevState) {
                return {
                    favoriteColor: nextProps.colorList[0] 
                }
            }

            //props: { colorList: [ 'red', 'blue', 'green' ] }
            //state: { favoriteColor: 'red' }
          
  • W praktyce metoda ta jest wykorzystywana bardzo rzadko.
  • Wywoływana jest przed render(), zarówno podczas montowania jak i aktualizacji.
  • Możemy ją wykorzystać do ustawienia stanu w zależności od props podczas każdej ich zmiany.
  • Powinna zwrócić obiekt, którym zostanie zaktualizowany stan lub null.
  • Brak dostępu do this.
  • W większości przypadków da się ją zastąpić używając memoizacji lub pisząc logikę w componentDidUpdate lub render

render

            // state: { isOpen: false }

              render() {
                  const { isOpen } = this.state
                  const { children} = this.props

                  return (
                      <div>
                          <button onClick={this.show}>Show</button>
                          <button onClick={this.hide}>Hide</button>
                          <button onClick={this.toggle}>{isOpen ? "Hide" : "Show"}</button>

                                      <div>Children list</div>
                                      {isOpen && children}
                      </div>
                  )
              }
          
  • Pozwala deklaratywnie opisać interfejs.
  • Zwraca rezultat wyrażeń JSX, poddrzewo Virtual DOM.
  • Nie można w nim wywoływać setState().

componentDidMount

            async componentDidMount() {
                const response = await axios.get('/some-url')
                this.setState({ data: response.data })
            }
          
  • Wywoływana jest po render() , po zamontowaniu komponentu w drzewie DOM.
  • W metodzie tworzymy zapytania HTTP, subskrybujemy się na zdarzenia i wykonujemy operacje na drzewie DOM.
  • Wywołanie setState() w tej metodzie spowoduje re-render - to normalne.
  • Może być definiowana z przedrostkiem async lub bez niego.

Etap aktualizacji

Aktualizacja może być wywołana zmianą state samego komponentu lub przekazywanych do niego props. Każda aktualizacja prowadzi to re-renderu komponentu i wywołania poniższych metod cyklu życia.

            shouldComponentUpdate(nextProps, nextState) {
                const oldProps = this.props

                if (nextProps.someProp === oldProps.someProp) {
                    return false;
                }
                return true;
            }
            // Re-render komponentu tylko, gdy zostały przekazane nowe propsy
          
  • Nie wywołuje się podczas montowania komponentu.
  • Wywołuje się tuż przed ponownym renderowaniem już zamontowanego komponentu.
  • Wymagana wyłącznie do optymalizacji procesu renderowania.
  • Domyślnie render zachodzi za każdym razem przy nowych props lub state. React nie sprawdza dokładnie nowych propsów, to jest czy są różne od poprzednich.
  • Pozwala porównać obecny i poprzedni state oraz props.
  • Zwrócenie wartości true lub false decyduje o ponownym renderze komponentu lub jego zatrzymaniu.
  • Jeśli zwróci false to nie zostanie wywołany render() i componentDidUpdate().
  • Domyślnie zwraca true
  • Nie można wywoływać setState().
  • Należy używać z rozwagą, dopiero po pomiarach wydajności, w przeciwnym razie może spowodować odwrotny efekt.
  • Przed użyciem należy wcześniej rozważyć zmianę dziedziczenia na React.PureComponent, który będzie powierzchownie porównywał props.
            getSnapshotBeforeUpdate(prevProps, prevState) {
              if (prevProps.list.length < this.props.list.length) {
                const list = this.listRef.current;
                return list.scrollHeight - list.scrollTop;
              }
              return null;
            }

            // Przechowujemy pozycję scrolla
          
  • W praktyce metoda ta jest wykorzystywana bardzo rzadko.
  • Wywoływana jest wtedy, gdy wszystkie zmiany są gotowe do dodania w DOM.
  • Można wykorzystywać do otrzymania wartości DOM przed aktualizacją, na przykład obecna pozycja suwaka lub rozmiar elementu przed aktualizacją.
  • To, co zwróci ta metoda, będzie przekazane jako trzeci parametr snapshot w componentDidUpdate().
            componentDidUpdate(prevProps, prevState, snapshot) {
                if (this.props.getMoreItems) {
                this.getMoreData().then(response => {
                  this.setState({
                    data: response.data,
                            scrollPosition: snapshot
                  })
                });
              }
            }
          
  • Wywołuje się zaraz po aktualizacji komponentu w DOM.
  • Nie wywołuje się podczas montowania komponentu.
  • Można wywoływać setState(), obowiązkowo opakowując go w warunek sprawdzający poprzednie i następne props lub state, aby nie powstał niekończący się cykl ponownego renderowania.
  • Można robić zapytania HTTP.

Etap odmontowywania

W pewnym momencie komponent zostanie usunięty z DOM. Wywoływana jest wtedy następująca metoda:

            // this.timerID = setInterval(() => { console.log('interval!') }, 1000);
            componentWillUnmount() {
              console.log("Clock", "componentWillUnmount");
              clearInterval(this.timerID);
            }
          
  • Wywołuje się tuż przed odmontowaniem komponentu i usunięciem elementu z DOM.
  • Świetnie nadaje się do sprzątania po sobie: subskrypcji zdarzeń, liczników czasu, zapytań HTTP. W przeciwnym razie pojawią się wycieki pamięci.
  • Nie ma sensu wywoływać setState(), komponent nie będzie już miał okazji do przerenderowania.

Obsługa błędów renderowania

React bardzo lubi zawieszać aplikacje, jeżeli wystąpił jakiś błąd. componentDidCatch działa w przypadku wystąpienia błędu w komponencie-dziecku i pozwala komponentom-rodzicom wyłapywać te błędy. To z kolei umożliwia ich obsługę np. wyświetlenie zapasowego interfejsu. Dzięki temu, w przypadku wystąpienia błędu, interfejs się nie zawiesi.

            componentDidCatch(error, info) {
              this.setState({
                  hasError: true,
                    errorMessage: error
              })
            }
          
  • Wykorzystuje się do kontroli błędów.
  • Wyłapuje błędy w dzieciach, ale nie w samym rodzicu.
  • error - rezultat toString() obiektu błędu
  • info - obiekt opisujący stack trace
            class ErrorBoundary extends React.Component {
              state = { hasError: false };

              componentDidCatch(error, info) {
            // Jeśli ta metoda została wywołana to gdzieś niżej w drzewie musiał wystąpić błąd
            // Ustanawiamy stan
                this.setState({ hasError: true });
            // Można także wysłać raport o błędzie do serwisu analitycznego
            // logErrorToMyService(error, info);
              }

              render() {
            // Jeżeli wystąpił błąd...
                if (this.state.hasError) {
            // Renderujemy fallback UI
                  return <h1>Something went wrong, please try again later :(</h1>;
                }

            // Jeśli wszystko OK, renderujemy dzieci
                return this.props.children;
              }
            }