Podział na komponenty w React.js

W tej części skupisz się na teorii i praktyce dzielenia zaprojektowanych aplikacji na poszczególne komponenty. Zaczniesz też tworzyć prostą appkę — menedżer kontaktów. W planach wyświetlanie, dodawanie i edycja kontaktów. Ale najpierw — musimy przecież zaprojektować HTML i CSS dla tej aplikacji.

Projekt

Przyjrzyj się temu co będziesz budował(a). Będzie to lista kontaktów, ale na początku spójrz tylko na pierwszy widok:

Komponenty w React.js i JSX

To dzisiaj „potniemy” i podzielimy na komponenty React.js w JSX.

Zacznij od napisania kodu HTML i CSS. W przykładzie wykorzystuję framework CSS semantic-ui, ale na dobrą sprawę z łatwością napiszesz wszystko w gołym CSS. Możesz też użyć bootstrapa — do woli. Oto kod HTML:

<header class="ui fixed menu">
  <nav class="ui container">
    <a href="#" class="header item">
      <img class="logo" src="https://typeofweb.com/wp-content/uploads/2017/08/cropped-typeofweb_logo-04-white-smaller-1-e1504359870362.png" />
      Lista kontaktów
    </a>
    <div class="header item">
      <button class="ui button">Dodaj</button>
    </div>
  </nav>
</header>
<main class="ui main text container">
  <ul class="ui relaxed divided list selection">
    <li class="item">
      <img src="https://api.adorable.io/avatars/55/typeofweb1.png" class="ui mini rounded image" />
      <div class="content">
        <h4 class="header">Lena</h4>
        <div class="description">JavaScript Developer</div>
      </div>
    </li>
    <li class="item">
      <img src="https://api.adorable.io/avatars/55/typeofweb2.png" class="ui mini rounded image" />
      <div class="content">
        <h4 class="header">Brian</h4>
        <div class="description">Human Resources</div>
      </div>
    </li>
    <li class="item">
      <img src="https://api.adorable.io/avatars/55/typeofweb3.png" class="ui mini rounded image" />
      <div class="content">
        <h4 class="header">Rick</h4>
        <div class="description">QA</div>
      </div>
    </li>
  </ul>
</main>

Jest tutaj nagłówek z logo i przyciskiem dodawania kontaktów oraz lista z trzema kontaktami. Wygląda dokładnie jak na screenshocie powyżej. Ale chcesz mieć to w React, prawda? Do dzieła!

Na razie nie skupiam się na sposobach używania kodu CSS do Reacta i po prostu dodałem <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.13/semantic.min.css"> do mojego kodu.,

Podział na komponenty w React.js

Do tej pory tworzyliśmy jeden komponent i renderowaliśmy go przez funkcję ReactDOM.render. Napisałaś/eś wtedy pewnie coś w stylu <App />. Czy to oznacza, że własne komponenty są także elementami? Tak jakby. Możemy ich używać w JSX tak, jakby nimi były. Czy to nie jest ekstra?

Myśląc o aplikacji postaraj się ją jakoś logicznie podzielić. Na jak najmniejsze fragmenty. To będą Twoje komponenty. Co możemy natychmiast wydzielić w naszej aplikacji? Oczywiście nawigację na górze oraz listę kontaktów. Świetna myśl! Dalej w oczy rzuca się możliwość odseparowania komponentu dla poszczególnych kontaktów — tak, aby nie powielać ich kodu. Dodatkowo, można by się pokusić o stworzenie osobnego komponentu dla avatarów — z tego względu, że zawarte jest w nim nieco logiki. Na schemacie wygląda to jakoś tak:

Schemat komponentów w React.js

Wszystko zaplanowane? Czas na kod!

Komponenty w React.js

Zacznij od stworzenia jednego komponentu w React i sprawienia, aby się wyświetlał. Potem przejdź do podziału. Pierwsze co musimy zrobić to zamienić wszystkie class na className w HTML. Potem już z górki:

function App() {
  return (
    <div>
      <header className="ui fixed menu">
        …
      </header>
      ……
    </div>
  );
}

To było proste, prawda? To już znasz. Teraz pozostaje wydzielić tylko pozostałe komponenty. Tworzymy AppHeader, ContactsListContactItem i wewnątrz nich odpowiedni kod. App ostatecznie będzie wyglądał tak:

function App() {
  return (
    <div>
      <AppHeader />
      <main className="ui main text container">
        <ContactsList />
      </main>
    </div>
  );
}
ReactDOM.render(, document.getElementById("app"));

AppHeader to łatwizna, więc nawet tutaj nie wrzucam. ContactsList jest nieco ciekawszy, bo wewnątrz używa kolejnego komponentu i przekazuje do niego propsy:

function ContactsList() {
  return (
    <ul className="ui relaxed divided list selection">
      <ContactItem
        login="typeofweb1"
        name="Lena"
        department="JavaScript Developer"
      />
      <ContactItem
        login="typeofweb2"
        name="Brian"
        department="Human Resources"
      />
      <ContactItem
        login="typeofweb3"
        name="Rick"
        department="QA"
      />
    </ul>
  );
}

Mamy tutaj komponent ContactsList, który tworzy listę i wewnątrz niej 3 komponenty ContactItem. Do nich przekazywane są odpowiednie propsy: Login, name i department, które służą do sparametryzowania tego co wyświetla komponent. W związku z tym ContactItem przyjmuje 3 propsy jako argument:

function ContactItem({ login, name, department }) {
  const imgUrl = `https://api.adorable.io/avatars/55/${login}.png`;
  return (
    <li className="item">
      <img src={imgUrl} className="ui mini rounded image" />
      <div className="content">
        <h4 className="header">{name}</h4>
        <div className="description">{department}</div>
      </div>
    </li>
  );
}

Poniżej efekt końcowy wraz z kodem:

See the Pen Props czyli atrybuty w React.js by Michał Miszczyszyn (@mmiszy) on CodePen.

Co dalej?

W kolejnych wpisach dodasz interakcję (np. kliknięcia) do komponentów. Dodatkowo poznasz stan (state), który potem przyda się nam przy rozbudowie aplikacji. Bez niego praktycznie niemożliwe byłoby tworzenie jakichkolwiek aplikacji, które oprócz wyświetlania treści miałyby robić cos jeszcze 🙂

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

Ćwiczenie

Ćwiczenie: Stwórz komponent do wyświetlania avatarów i przenieś do niego kod za to odpowiedzialny. Niech ten komponent przyjmuje jako props tylko login. Wrzuć swój kod w komentarzu!

Ćwiczenie*: Zmodyfikuj stworzony komponent z avatarem tak, aby obecne obrazki były wyświetlane gdy login nie jest mailem. Natomiast gdy jest mailem to skorzystaj z Gravatara. Zauważ, że całkowita zmiana działania tego komponentu nie wymaga wprowadzania żadnych zmian w pozostałym kodzie aplikacji!

  • Karol Grabowski

    Hejka, bardzo fajna seria artykułów do reacta, sam aktualnie jestem w trakcie nauki i sie bardzo przydają. Mam takie pytanie, czy jak tworzymy bezstanowy komponent to lepiej korzystac z klas (tak to jest często robione w przykładach w dokumentacji reacta ) czy lepiej uzyc jednak zwyklych funkcji, ma to jakiś wpływ np na wydajność?

  • Bartek

    Super czekam na więcej

  • Wiesław Turzański

    nie wiem na jaką ocenę wykonałem zadanie domowe. Ale dopiero jestem w „przedszkolu w gupie REACT-ki”.

    function ContactItem({ login, name, department }) {
    return (

    {name}
    {department}

    );
    }

    function Avatar(avatar) {
    const isEmail = (avatar.login).includes("@");

    if (isEmail) {
    const grav = "https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50";
    return
    }
    else {
    const imgUrl = `https://api.adorable.io/avatars/55/${avatar.login}.png`;
    return
    }

    Pozdrawiam

    • Mam kilka uwag, ale mimo to wygląda bardzo dobrze 🙂 Świetna robota!

      – nazwy własnych komponentów, dla bezpieczeństwa, powinny być co najmniej dwoma słowami. Czyli np. Avatar nie, a zamiast niego UserAvatar — już lepiej. Pozwoli to ewentualnie łatwiej uniknąć konfliktów w przyszłości.
      – w kodzie masz avatar, a powinno być z wielkiej litery
      className — musi być z wielką literą w środku
      – w komponencie z avatarem też można użyć destrukturyzacji
      src={avt} — bez cudzysłowów. W tej sposób React odróżnia czy to co przekazujesz jest zmienną czy tylko stringiem

      Ostatecznie kod mógłby wyglądać tak:

      function ContactItem({ login, name, department }) {

      return (

      <li classname="item">

      <UserAvatar login={login} />

      <div classname="content">

      <h4 classname="header">{name}</h4>

      <div classname="description">{department}</div>

      </div>

      </li>

      );

      }

      function UserAvatar({ login }) {

      let avt;

      if (login.includes("@")) {

      avt = "https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50";

      } else {

      avt = `https://api.adorable.io/avatars/55/${login}.png`;

      }

      return <img src={avt} classname="ui mini rounded image" />

      }

      Oczywiście do gravatara należałoby przekazać hash z maila (md5), ale to tutaj pominę. Można by się też zastanowić czy nie przenieść tej logiki do jeszcze osobnej funkcji (i osobnego pliku). Zawsze użycie let jest czymś co powinno zmusić do zastanowienia 🙂

      • Wiesław Turzański

        Dziękuję bardzo za cenne uwagi. Oczywiście zaraz kod dostosuję zgodnie z sugestiami. Co do className to w kodzie mam prawidłowo, ale nie za bardzo wiem jak tutaj wkleić mój kod, tak aby był oryginalny. Wyżej użyłem znaczników code/code i tam wkleiłem swój kod. Ale to nie jest chyba dobra rozwiązanie. Jeszcze raz dzięki za naukę i podrawiam serdecznie. 🙂

  • Cześć,
    Dziękuję za cały kurs, jest napisany w bardzo przystępny sposób.
    Mamy pytanie dotyczące proposów; dlaczego nie mogę przekazać w propsie całego url do zdjęcia? np:

    function ListElement ({imgUrl, name, description}) {
    return (

    {name}
    {description}

    )
    }

    Pytanie to łącze z ćwiczeniem do samodzielnego rozwiązania, które kończy poprzedni temat. Co można, a czego nie przekazać w propsie (czy tylko stringa?)

    Pozdrowienia 🙂

    • A kto powiedział, że nie możesz przekazać w propsie całego URL-a? Możesz. Możesz przekazać co chcesz.
      Ale w tym przypadku sensowniej jest dla mnie, żeby do komponentu kontaktu przekazywać tylko dane kontaktu — URL nie jest daną 😛 Zresztą odnosi się do tego ćwiczenie, w którym URL się zmienia w zależności od zawartości loginu.

      Spójrzmy na to jeszcze inaczej: Gdyby nagle miał się zmienić sposób wyświetlania avatarów i zamiast URL-a do gravatara chciałbyś mieć URL-a do innego serwisu. W moim przykładzie wystarczy zmiana komponentu ContactItem. W sugerowanym przez Ciebie rozwiązaniu natomiast, gdy URL jest przekazywany jako props, musiałbyś odnaleźć wszystkie komponenty, które używają ContactItem i je pozmieniać. To złamanie elementarnych zasad dobrej architektury.

      Co do stringów — jako props możesz przekazać co chcesz, stringi, obiekty, funkcje.

      • Dziękuję bardzo za odpowiedź!
        Wszystko jasne i zrozumiałe 🙂

  • Cześć,
    Wrzucam moje kody. Będę wdzięczna za komentarz.


    function MainContent () {
    return (

    )
    }
    function ListElement ({avatarLogin, name, description}) {
    return (

    {name}
    {description}

    )
    }
    function UserAvatar ({login}) {
    let imgUrl='';
    if (login.includes('@') ) {
    imgUrl = `https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50`;
    } else {
    imgUrl = `https://api.adorable.io/avatars/55/${login}.png`;
    }
    return (

    )
    }

    Pozdrowienia!

    • Coś tu z wielkością znaków jest nie tak 😉 powinno być <ListElement i tak dalej. Wielkość znaków ma znaczenie.

      Poza tym wygląda spoko 😉

      • a tak, jakoś po przyklejeniu do komentarza, znaki zmieniły wielkość .
        Dzięki! 🙂