4 min czytania

React Hooks Part 1 - useEffect

React Hooks Part 1 - useEffect
Photo by Hendrik Morkel / Unsplash

Minęły już prawie 3 lata od kiedy React Hooksy po raz pierwszy zagościły w wersji React 16.8. Jedni pokochali je od razu, drudzy potrzebowali trochę czasu na oswojenie. W tym materiale przedstawię wam oraz spróbuje wytłumaczyć działanie najczęściej używanych hooksów z jakimi będziecie mieli do czynienia w projektach reactowych. Na początek zajmijmy się funkcją, która niezrozumiana może narobić trochę kłopotów początkującym programistom.

Pamiętaj proszę, że wszystkie przykłady które Ci zademonstruje w tym poście nie są przykładami produkcyjnymi. Zostały one maksymalnie uproszczone tak abyś skupił się na tym co ważne a nie zastanawiał się co robi akurat ten if i dlaczego został on tak napisany.

useEffect

O UseEffect'ie mówi się jak o zamienniku "starego" klasowego componentDidMount. Poniekąd jest to prawda chociaż nie tylko na tym kończy się jego rola.

useEffect( () => {

// do something

})

Hooka efektów używa się głównie do wykonania jakiejś czynności, jakiegoś side-effectu, po tym jak komponent się już zbuduje i "narysuje". Po wyrenderowaniu DOM'a zostanie wywołany nasz useEffect a ściśle rzecz biorąc funkcja którą do niego przekazaliśmy.

Dependency array

A co kiedy nie chcemy aby za każdym przeładowaniem naszego komponentu dany efekt się wywoływał? W tym przypadku dostaliśmy do dyspozycji tablice zależności - dependency array.

Żeby łatwiej zwizualizować sobie jak tak naprawdę zbudowany jest useEffect uprośćmy go sobie maksymalnie.

useEffect(funkcja, tablica zależności)

UseEffect jest przede wszystkim funkcją, której pierwszym argumentem jest kolejna funkcja, która wywoła nam jakiś efekt. Drugim argumentem jest tablica zależności, od których będzie zależało to czy nasza funkcja się wywoła czy nie.

W przypadku pierwszego argumentu tutaj nie mamy co dyskutować, należy zawsze podać, inaczej nie miałoby to żadnego sensu prawda? Natomiast w przypadku tablicy zależności, no tutaj sprawa ma się trochę inaczej. Szybkie spojrzenie na implementacje w pliku źródłowym daje nam cenną informację :

function useEffect(effect: EffectCallback, deps?: DependencyList): void;

Drugi argument przekazywany do hooka efektu jest opcjonalny, co oznacza że możemy go przesłać albo nie. Ale co to TAK NAPRAWDĘ oznacza? Co jeżeli go nie podam?

Deklarować dependency array czy nie?

Przede wszystkim nie powinieneś nie podawać tablicy zależności. Takie podejście jest przez wielu uważane za błędne i doprowadzające do zachowań, które nierzadko ciężko nam przewidzieć. Dlaczego? Otóż niepodanie tej tablicy będzie skutkowało tym, że za każdym przeładowaniem naszego komponentu wywoła się nasz efekt.

No ale co z tego? Mam małą funkcyjke, która sobie coś tam sprawdza i tyle, niech wykonuje się za każdym razem, nie bardzo mnie to boli. Warto zastanowić się czy tak naprawdę jest.

Wyobraź sobie, że mamy komponent Radio, który ma wszystkie cechy radia. Możemy słuchać muzyki, podgłaśniać i ściszać dźwięk, zmieniać stacje, ustawiać basy i robić wszystko to co możesz robić w swoim własnym radiu. W poniższym przykładzie chcemy zwiększyć głośność w momencie kiedy akurat leci nasza ulubiona piosenka - Parostatek na 80%. W innym wypadku niech głośność będzie ustawiona na 40%.

useEffect( () => {

 if(song === 'Parostatek') {
   setVolume(80) // 80 percent
  } else {
   setVolume(40) // 40 percent
  }
  
}, [song])

Powyższa funkcja nasłuchuje, przy każdym przerenderowaniu komponentu, czy wartość song się zmieniła porównując ją do poprzednio zapamiętanej wartości. Jeżeli wartość po przerenderowaniu jest inna niż ta którą pamięta, wywoła nam się nasz efekt.

Patrząc na powyższy przykład możesz zapytać dlaczego nie pozwolimy aby ten efekt wywoływał się za każdym razem? Przecież piosenki w radio zmieniają się raptem co pare minut, a ta funkcja nie robi nic wielkiego więc dlaczego nie wywalić tablicy zależności i pozwolić jej wywoływać się za każdym razem przy przeładowaniu komponentu?

Dobrze, a co jeżeli "wywołamy" inną funkcje związaną z używaniem radia? Czy chcemy aby nasza funkcja wywoływała się gdy ściszymy radio? Albo gdy zwiększymy basy? Nie. Dlatego jasno daliśmy Reactowi do zrozumienia, że chcemy aby nasza funkcja została odpalona tylko w momencie kiedy zmieni się wartość przechowywana w zmiennej song.

"Nowy" componentDidMount

Jak wspominałem wyżej, useEffect jest często nazywany funkcyjnym zamiennikiem jednego ze stanu cyklu życia komponentu klasowego jakim jest componentDidMount. Okej, potrzebujemy wywołać coś raz i tylko raz w momencie pierwszej inicjalizacji komponentu. Jak to napisać wykorzystując useEffect ?

useEffect( () => {

// do something only once when component is
// created for the first time

}, [] )

Odpowiedź brzmi: poprzez podanie pustej tablicy zależności. Powyższa funkcja wywoła nam się tylko i wyłącznie w momencie pierwszej inicjalizacji naszego komponentu i będzie ignorować wszelkie dalsze przeładowania.

Posprzątaj po sobie

Podczas cyklu życia naszej funkcji wielokrotnie ja przeładowujemy, zmieniamy wartości, wyświetlamy różne dane. W skrócie, robimy dużo "bałaganu". Jedną z rzeczy która potrafi narobić bałaganu jest subskrypcja. Po tym gdy nasz komponent "wyrazi chęć" na otrzymywanie w ramach subskrybcji nowych informacji, to w momencie kiedy nie będzie nam już potrzebny i będziemy chcieli go usunąć, należało by po sobie posprzątać i nacisnąć guzik "unsubscribe". W innym wypadku może dojść do bardzo niepożądanego wycieku pamięci.

Szybkie wprowadzenia co to jest subskrypcja w programowaniu. Upraszczając, pojęcie subskrypcji oznacza chęć na otrzymywanie powiadomień (nowych wartości) gdy coś co nas interesuje (czyli to do czego się subskrybujemy) się zmieni. Przykład z życia wzięty - macie swojego ulubionego youtubera który wypuszcza filmiki, subskrybujecie go aby dostawać powiadomienia za każdym razem gdy na jego kanale pojawi się nowy filmik. Tak to mniej więcej działa i tu.

useEffect( () => {

  ChartApi.subscribe()

},[])

Na przykładzie widzimy jak podczas pierwszego zbudowania się naszego komponentu subskrybujemy się do ChartAPI, która przypuśćmy podsyła nam co chwilę nowe wartości do wyświetlenia na naszym wykresie. W ten sposób nasz wykres "żyje", dostaje nowe świeże dane które użytkownik widzi na bieżąco. Fajna sprawa, ale wymaga od nas przygotowania się na sytuację w której nasz komponent, który się subskrybuje do ChartAPI, przestanie nam być potrzebny. Czyli co? Czyli trzeba po sobie posprzątać.

useEffect( () => {

  ChartApi.subscribe()
  
    // cleaning function
  return () => {
	ChartAPI.unsubscribe()
  }

},[])

Aby zrobić to prawidłowo należy wywołać funkcję return podając jej co ma stać się w momencie gdy nasz komponent zostanie 'odmontowany' (unmounted). W ten sposób zabezpieczamy się przed nierzadko tragicznym w skutkach wycieku pamięci, ponieważ mimo że komponent już nie istnieje w dalszym ciągu nasz subskrybent próbuje wysyłać mu nowe informacje.