Wprowadzenie React #4 (2020) – stan komponentu i przepływ danych

W poprzednim wpisie przyjrzeliśmy się jak obsługiwać zdarzenia z poziomu React, a w tym wykorzystamy tę wiedzę, by rozszerzyć funkcjonalność naszej strony i dowiemy się czym jest bardzo ważna rzecz – stan komponentu.

Nowy komponent – Showcase

Zacznijmy od stworzenia nowego komponentu o nazwie Showcase, w którym będziemy wyświetlać w większym rozmiarze obraz, na który kliknie użytkownik.


Pozostałe części cyklu:

  1. Utworzenie projektu i pierwszy komponent
  2. Komponenty i props
  3. Obsługa zdarzeń
  4. Stan komponentu i przepływ danych
  5. Dynamiczne tworzenie komponentów, key i forumlarz
  6. Hook useEffect
  7. Zapisywanie danych w chmurze – Firebase
  8. Wdrożenie aplikacji na zewnętrznym serwerze – Heroku

Tutorial React w formie wideo dostępny jest na kursy.clockworkjava.pl


Zacznijmy od wersji ze statycznym adresem grafiki.

export const Showcase = () => {
  return (
    <img
      src="https://images.unsplash.com/photo-1580109672851-b85640868813"
      height="525"
      width="600"
    ></img>
  );
};

export const App = () => {
  return (
    <div onClick={event => console.log("Event from div")}>
      <Image url="https://images.unsplash.com/photo-1508138221679-760a23a2285b" />
      <Image url="https://images.unsplash.com/photo-1474487548417-781cb71495f3" />
      <Image url="https://images.unsplash.com/photo-1580109672851-b85640868813" />
      <Image url="https://images.unsplash.com/photo-1580046939256-c377c5b099f1" />
      <Image url="https://images.unsplash.com/photo-1576801488695-2e4d7a14b8b5" />
      <Showcase />
    </div>
  );
};

Wszystko fajnie, jednak chcemy, by adres URL się zmieniał w zależności od klikniętego obrazu, więc zmieniał się.

Tak przy okazji… obrazki będą ładować się wolno, bo są w gigantycznej rozdzielczości 🙂

Stan komponentu

Stan jest spokrewniony z propsami. Jest to czysto Reactowa rzecz. O ile propsy przychodzą z góry jako właściwości komponentu przy jego tworzeniu, to state może się zmieniać w zależności od tego, jakie akcje wykona użytkownik. Dodatkowo każda zmiana stanu powoduję ponowne wyrysowanie komponentu w oknie przeglądarki, czyli pozwala na zmianę tego, co widzi użytkownik bez konieczności przeładowania strony.

Stan w komponentach funkcyjnych (a na nich się w tym tutorialu koncentrujemy) tworzymy za pomocą metody useState.

Jest to jeden z podstawowych hooków. Tak zwane hooki w React to specjalne funkcje, które zostały dodane w wersji 16 tej biblioteki. Dzięki nim komponenty funkcyjne zyskały o wiele więcej możliwości, bo wcześniej stan i ogrom innych możliwości był zarezerwowany tylko dla komponentów klasowych, które wymagały więcej kodu do utworzenia.

useState jako argument przyjmuje domyślną (pierwszą) wartość, jaką dany stan ma przyjąć, a zwraca dwie rzeczy: zmienną, pod którą dany stan będzie dostępny, oraz funkcję, za pomocą której będziemy mogli wartość stanu zmienić.

import React, { useState } from "react";
import ReactDOM from "react-dom";

//...

export const Showcase = () => {
  const [url, setUrl] = useState(
    "https://images.unsplash.com/photo-1580109672851-b85640868813"
  );

  return <img src={url} height="525" width="600"></img>;
};

Dodaliśmy import, by móc korzystać z funkcji useState, a następnie użyliśmy jej, by utworzyć nowy stan – url. Dodatkowo dostajemy odnośnik do kolejnej funkcji – setUrl, która będzie wykorzystywana do ustawienia stanu.

Rozbudujmy jeszcze trochę nasz komponent Showcase, dodajmy użycie metody setUrl

export const Showcase = () => {
  const [url, setUrl] = useState(
    "https://images.unsplash.com/photo-1580109672851-b85640868813"
  );

  return (
    <>
      <img src={url} height="525" width="600"></img>
      <button
        onClick={e =>
          setUrl("https://images.unsplash.com/photo-1580046939256-c377c5b099f1")
        }
      >
        First
      </button>
      <button
        onClick={e =>
          setUrl("https://images.unsplash.com/photo-1508138221679-760a23a2285b")
        }
      >
        Second
      </button>
    </>
  );
};

Dodaliśmy dwa guziki. Po kliknięciu zmienia się obraz wyświetlany przez komponent Showcase. Bez przeładowania strony. Wszystko dzięki użyciu metody setUrl, która zmienia stan, co powoduje przerysowanie strony, a w efekcie wyświetlenie innego obrazu.

Wszystko fajnie, ale nas interesuje, by użytkownik kliknął komponent Image, a w efekcie komponent Showcase wyświetlił odpowiedni obraz. Nie interesują nas dodatkowe guziki.

Przepływ danych w React

Co możemy zrobić to wychwycić zdarzenie kliknięcia komponent Image, dobrać się do wartości właściwości url i przesłać ją do komponentu Showcase. O ile pierwszą część jesteśmy w stanie już zrobić, to przesłanie danych pomiędzy komponentami nie jest na pierwszy rzut oka takie oczywiste (choć na drugi jest już bardzo fajnym i “czystym” rozwiązaniem).

Obecnie drzewo komponentów aplikacji wygląda tak:

Drzewo komponentów react

Mamy komponent App, który zawiera pewną ilość komponentów Image oraz jeden komponent Showcase.

Chcemy przesłać dane z komponentu Image do komponentu Showcase. W obecnej sytuacji jest to jednak w React niemożliwe. Dlaczego?

W React dane możemy przesyłać tylko w dół drzewa DOM, czyli do komponentów, które nasz komponent zawiera, są jego bezpośrednimi potomkami. Robimy to za pomocą props. Nie ma możliwości przesyłania ani w górę drzewa, do rodzica, ani do swoich braci i sióstr.

W React dane przesyłane są tylko w dół drzewa komponentów.

Podejście do rozwiązania naszego problemu jest następujące: dane, które chcemy współdzielić pomiędzy rodzeństwem ustawiamy jako stan ich rodzica, a następnie przesyłamy do potomstwa ów stan i, jeśli jest taka potrzeba, funkcję do jego zmiany.

Wracając na grunt naszej galerii. Chcemy współdzielić (ustawiać w jednym komponencie, a czytać w drugim) adres URL obrazu, który ma być wyświetlany w Showcase. Ustawmy więc ten stan na poziomie komponentu-rodzica, czyli komponentu App.

export const App = () => {
  const [showcaseURL, setShowcaseURL] = useState("");

  return (
    <div>
      <Image url="https://images.unsplash.com/photo-1508138221679-760a23a2285b" />
      <Image url="https://images.unsplash.com/photo-1474487548417-781cb71495f3" />
      <Image url="https://images.unsplash.com/photo-1580109672851-b85640868813" />
      <Image url="https://images.unsplash.com/photo-1580046939256-c377c5b099f1" />
      <Image url="https://images.unsplash.com/photo-1576801488695-2e4d7a14b8b5" />
      <Showcase />
    </div>
  );
};

Następnie chcemy by stan ten ustawiany był po kliknięciu komponent Image. Propsy mają tę zaletę, że możemy tam wysyłać nie tylko wartości prymitywne czy obiekty, ale też i funkcje. Możemy więc do komponentu Image wysłać funkcję służącą do ustawiania stanu showcaseURL i wywołać ją po kliknięciu w tag img.

export const Image = ({ url, setShowcaseURL }) => {
  const handleOnClick = event => {
    event.preventDefault();
    event.stopPropagation();
    setShowcaseURL(url);
  };

  return <img src={url} height="175" width="200" onClick={handleOnClick}></img>;
};

export const App = () => {
  const [showcaseURL, setShowcaseURL] = useState("");

  return (
    <div>
      <Image
        url="https://images.unsplash.com/photo-1508138221679-760a23a2285b"
        setShowcaseURL={setShowcaseURL}
      />
      <Image
        url="https://images.unsplash.com/photo-1474487548417-781cb71495f3"
        setShowcaseURL={setShowcaseURL}
      />
      <Image
        url="https://images.unsplash.com/photo-1580109672851-b85640868813"
        setShowcaseURL={setShowcaseURL}
      />
      <Image
        url="https://images.unsplash.com/photo-1580046939256-c377c5b099f1"
        setShowcaseURL={setShowcaseURL}
      />
      <Image
        url="https://images.unsplash.com/photo-1576801488695-2e4d7a14b8b5"
        setShowcaseURL={setShowcaseURL}
      />
      <Showcase />
    </div>
  );
};

Dodatkowo w komponencie App, przy tworzeniu komponentów Image dodajemy propsa setShowcaseURL i jako jego wartość podajemy funkcję do zmiany stanu, którą zwrócił nam useState.

Pomimo, że funkcja zostanie wywołana w innym komponencie, niż została zdefiniowana, to wciąż samo wywołanie będzie miało miejsce w kontekście komponentu App i zmieni nam jego stan.

Ostatnią rzeczą do zrobienia jest przesłanie url klikniętego obrazu do komponentu Showcase jako props. Przy okazji wywalimy zbędne buttony.

export const Showcase = ({ url }) => {
  return (
    <>
      <img src={url} height="525" width="600"></img>
    </>
  );
};

W komponencie App przesyłamy wartość stanu showcaseURL jako props url.

export const App = () => {
  const [showcaseURL, setShowcaseURL] = useState("");

  return (
    <div>
      <Image
        url="https://images.unsplash.com/photo-1508138221679-760a23a2285b"
        setShowcaseURL={setShowcaseURL}
      />
      //...
      <Showcase url={showcaseURL} />
    </div>
  );
};

Gdy teraz odpalimy naszą aplikację za pomocą npx parcel ./src/index.html w końcu będzie ona działać jak należy

Podsumowywując:

  • Stan komponentu to wartość wewnętrzna danego komponentu, której to zmiana powoduje przerysowanie komponentu.
  • Do utworzenia stanu i funkcji go zmieniającej używamy metody useState z biblioteki react.
  • W React dane mogą być przesyłane do innych komponentów tylko w dół drzewa DOM, za pomocą props.
  • Jeśli jakieś komponenty muszą współdzielić stan, wówczas przenosi sie go do wspólnego komponentu nadrzędnego.
  • Jako props możemy przesyłać funkcję, a szczególne funkcje do ustawiania stanu

Kod przykładu dostępny jest na Github.

radycyjnie zapraszam do dołączenia do newslettera, gdzie materiały odnośnie React również mają swoje miejsce.

3 myśli w temacie “Wprowadzenie React #4 (2020) – stan komponentu i przepływ danych”

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *