Setki lampek zwisających z oświetlonego sufitu

Komponenty funkcyjne i props – Pierwszy projekt w ReactJS #3

Znajomość elementów React oraz JSX otwiera przed nami wrota do świata komponentów. To właśnie wokół nich kręci się cały proces budowania aplikacji w ReactJS. Czas rzucić trochę światła na to, czym komponenty są, jak z nich korzystać oraz jakie wyróżniamy ich rodzaje.

Z czysto technicznego punktu widzenia, komponenty to nic innego jak funkcje lub klasy.Opcjonalnie przyjmują dane wejściowe w postaci obiektu props, ich danymi wyjściowymi są elementy React.

Proces wydzielania komponentów z całości interfejsu rządzi się podobnymi prawami, co tworzenie procedur w ramach klasy. Przede wszystkim, komponent powinien zajmować się jedną, konkretną częścią widoku.

Aby ułatwić sobie proces projektowania komponentów, dzieli się je na różne rodzaje. Punktem wyjścia dla tych podziałów są kategorie techniczne i funkcjonalne.

Najprostszy podział wynika ze sposobu zadeklarowania komponentu. Z tego punktu widzenia wyróżniamy komponenty funkcyjne i klasowe.

Ten wpis będzie poświęcony tytułowym komponentom funkcyjnym. Komponenty klasowe dostaną swoje pięć minut w przyszłym tygodniu.

Cechy charakterystyczne komponentów funkcyjnych:

  • otrzymują dane od rodziców za pośrednictwem obiektu props
  • są pozbawione stanu
  • zajmują się prezentacją a nie logiką aplikacji

Przyjrzyjmy się tym trzem cechom z bliska.

Let the data flow

props to obiekt, zawierający wszystkie atrybuty JSX, przekazane do instancji komponentu przez rodzica. Komponenty wykorzystują props podobnie jak elementy HTML wykorzystują atrybuty.

props pozwala na opisanie szczegółów implementacyjnych danej instancji komponentu. Dzięki temu możemy pozbyć się jawnego wskazywania tych szczegółów w jego definicji.

Dane wpisane na sztywno to zasadniczy problem utworzonego w poprzednim wpisie prototypu komponentu. Póki co, nadaje się on jedynie do wyświetlania informacji o Bitcoinie.

Dzięki props zyska wszechstronność i będzie zdatny do wielokrotnego użytku.

Do props możemy przekazać dowolną, poprawną wartością JS. Liczby, obiekty, tablice, boole, funkcje etc. Komponenty mają znacznie szerszy wachlarz możliwości niż elementy HTML, ograniczające się wyłącznie do stringów.

Komponenty funkcyjne są pozbawione stanu

props zawsze napływają do komponentu z zewnątrz. Drugim obiektem, który służy do zarządzania danymi w komponentach, jest state. Dzięki nim komponent może zarządzać stanem od wewnątrz. state jest dostępne wyłącznie w komponentach klasowych.

Z tego powodu komponenty funkcyjne często określa się inną, dość rozwlekłą w języku polskim, nazwą: funkcyjne komponenty pozbawione stanu (ang. functional stateless components, FCS).

Po co decydujemy się na korzystanie z komponentów, które mają jawnie ograniczony zakres możliwości?

Jak się okazuje, te ograniczenia mają swoje zalety.

Komponenty funkcyjne cechuje prostota, zwięzłość i przewidywalność. Nie musimy przejmować się stanem ani innymi funkcjonalnościami, dodającymi zbędną złożoność.

Komponenty funkcyjne możemy traktować jak funkcje czyste. Przekazanie takich samych danych wejściowych (props), wiąże się ze zwróceniem takich samych danych wyjściowych (element React).

Dzięki temu możemy cieszyć się wszystkimi zaletami programowania funkcyjnego. Zmniejszona podatność na błędy. Ułatwiony proces testowania. Czego chcieć więcej.

Komponenty pozbawione stanu są świetnym sposobem na redukowanie złożoności aplikacji. Zawsze powinny być punktem wyjścia dla projektowanych przez Ciebie komponentów. Jeżeli dojdziesz do wniosku, że komponent wymaga dodatkowych funkcjonalności, wtedy przebuduj go na klasowy odpowiednik.

Takie podejście zaprocentuje w przyszłości. Zespół React zapowiedział, że w nadchodzących wersjach biblioteki, komponenty funkcyjne będą wydajniejsze od komponentów klasowych.

Prezentacja vs logika

Różnice pomiędzy funkcjonalnościami dostępnymi dla komponentów funkcyjnych i klasowych, znajdują swoje odzwierciedlenie w ich zastosowaniu.

Funkcji używa się przede wszystkim do prezentowania danych napływających z props. Logika aplikacji i zarządzanie przepływem danych należą do obowiązków klas.

Zobaczmy, jak wygląda to w praktyce na przykładzie komponentu Coin.

Wypadałoby zacząć od przekształcenia prototypu w komponent z prawdziwego zdarzenia. Pozwoli na to wykorzystanie props.

// Coin.js
const Coin = (props) => (
<div>

{props.name}
<ul>
 	<li>Acronym: {props.acronym}</li>
 	<li>Current value: ${props.value}</li>
 	<li>Market cap: ${props.cap}</li>
</ul>
</div>
);

export default Coin;

Pozbyliśmy się wszystkich danych wprowadzonych na sztywno. Ich miejsce zajęły referencje do właściwości props otoczone nawiasami klamrowymi.

W komponentach funkcyjnych dostęp do props uzyskujemy za pośrednictwem pierwszego parametru funkcji. Teoretycznie możesz nazwać go w dowolny sposób, ale proponuję trzymać się wzorców.

props to obiekt tylko do odczytu. Każdy komponent powinien zachowywać się jak funkcja czysta w stosunku do props. Co to oznacza w praktyce? Nigdy nie powinieneś modyfikować tego obiektu. Wszelkie próby zostaną nagrodzone błędem ;).
Błąd wyrzucany przy próbie mutacji props

Dobre praktyki pracy z komponentami funkcyjnymi

Od tej pory Coin nadaje się do prezentowania dowolnej kryptowaluty.

Aby zwiększyć czytelność komponentu, skorzystajmy z destrukturyzacji props.

const Coin = ({name, acronym, value, cap}) => (
<div>

{name}
<ul>
 	<li>Acronym: {acronym}</li>
 	<li>Current value: ${value}</li>
 	<li>Market cap: ${cap}</li>
</ul>
</div>
);

export default Coin;

Pozbyliśmy się zbędnych referencji do props. Ich miejsce zajęły bezpośrednie odwołania do interesujących nas właściwości.

Dzięki temu komponent jasno sygnalizuje, jakie atrybuty JSX są oczekiwane przy tworzeniu instancji.

Załóżmy, że chcemy utworzyć instancję komponentu, który podobnie jak nasz dotychczasowy prototyp, będzie prezentował stan Bitcoina. Musimy przekazać do instancji następujące dane:

props = {
name: "Bitcoin",
acronym: "BTC",
value: 11000,
cap: 188698026949
}

Jak to zrobić? Wystarczy przypisać odpowiednie atrybuty JSX do instancji.

// App.js
<Coin name="Bitcoin" acronym="BTC" value={11000} cap={188698026949} />

Jak widzisz, wartości, które nie są stringami, należy przekazywać w nawiasach klamrowych.

Wartości value i cap potrafią być całkiem zawrotne. Aby zwiększyć ich czytelność, wypadałoby dodać dodatkowe formatowanie w Coin.js. Z pomocą wyrażenia regularnego /\B(?=(\d{3})+(?!\d))/g i funkcji String.prototype.replace(), dodałem odpowiednie odstępy pomiędzy znakami.

<ul>
 	<li>Current value: ${String(value)
.replace(/\B(?=(\d{3})+(?!\d))/g, " ")
.trim()}</li>
 	<li>Market cap: ${String(cap)
.replace(/\B(?=(\d{3})+(?!\d))/g, " ")
.trim()}</li>
</ul>

Zróbmy użytek z faktu, że Coin jest zdatny do wielokrotnego użytku. Aplikacja będzie wyglądała ciekawiej z Etherum, NEO i EOS na pokładzie.

<Coin name="Etherum" acronym="ETH" value={842} cap={82581287147} />	 	 
<Coin name="NEO" acronym="NEO" value={117} cap={7624760000} />	 	 
<Coin name="EOS" acronym="EOS" value={7} cap={5513844716} />

Ważne: Aby React odróżnił komponent od zwykłego tagu HTML, należy rozpoczynać nazwy jego instancji z dużej litery.

Oddajemy komponent w cudze ręce

Przypuśćmy, że nabyta wiedza o komponentach funkcyjnych, zainspiruje Was do stworzenia trackera kryptowalut w ramach serii na blogu o ReactJS (ZDARZA SIĘ). Nie można oczekiwać, że programiści zawsze będą korzystali z Waszych komponentów we właściwy sposób.

Rozwiązaniem tego problemu jest biblioteka PropTypes.

Ma ona dwie użyteczne funkcjonalności znacznie usprawniające prace zespołową. Dzięki PropTypes zyskujemy możliwość walidowania typów oraz przypisania wartości domyślnych props.

Dzięki PropTypes możemy jasno zasygnalizować nasze oczekiwania co do typu danych napływających do komponentu z zewnątrz. Wszelkie pomyłki i zaniechania zostaną nagrodzone wyrzuceniem ostrzeżeń do konsoli. Ułatwia to komunikację na linii twórca -> użytkownik.

Aby korzystać z PropTypes w projekcie, musisz zainstalować pakiet biblioteki.

npm install --save prop-types;

Następnie pozostało zaimportować moduł do przestrzeni Coin.js.

import PropTypes from 'prop-types';

Możemy działać.

Walidacja props

Aby wskazać oczekiwany typ właściwości props, musimy przypisać do komponentu specjalną właściwość propTypes.

propTypes to obiekt przechowujący nazwy właściwości props i oczekiwane typy ich wartości.

// Coin.js

// Kod komponentu

Coin.propTypes = {
name: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
cap: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
};

Ten skromny przykład zdecydowanie nie wyczerpuje szerokiego zakresu możliwości

PropTypes. Możesz je sprawdzić w dokumentacji biblioteki.

Oprócz walidowania typu właściwości, mamy również możliwość zaznaczenia, że jej przekazanie jest obowiązkowe. Pozwala na to .isRequired.

// Coin.js

// Kod komponentu

Coin.propTypes = {
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
cap: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
};

Wartości domyślne

PropTypes pozwala również zabezpieczyć się na wypadek całkowitego pominięcia właściwości.

Jaka jest przewaga zewnętrznego rozwiązania nad wbudowanym operatorem OR?

Po pierwsze, nie musimy uciekać się do rozwiązań, którymi ratowali się nasi przodkowie w zamierzchłej epoce ES5. Po drugie, PropTypes oddziela wartości domyślne od wyświetlania zawartości, co zwiększa czytelność naszego komponentu.

Coin.defaultProps = {
value: "-"
acronym: "0",
cap: "0"
}

W ramach testów stwórzmy „pustą” instancję Coin.

Wartości domyślne wraz z komunikatem o pominięciu wymaganej właściwości name

Wartości domyślne zostały zastosowane, ale silnik przypomniał o wymogu przekazania właściwości name. Wszystko działa jak należy.

Zadanie domowe

Dodaj props do komponentu Header. Skorzystaj z destrukturyzacji. Pamiętaj o formatowaniu oraz walidacji otrzymanych danych.

Pamiętaj, że to świetna okazja do sprawdzenia, ile informacji przyswoiłeś. Postaraj się wykonać zadanie samodzielnie. Rozwiązanie znajdziesz tutaj.

Materiały uzupełniające

Tym razem mam co nieco dla czytelników spragnionych dodatkowych informacji:

Podsumowanie

W kolejnych wpisach wszystko i jeszcze więcej o komponentach klasowych. Dowiesz się, jak zarządzać stanem z wnętrza komponentu, czym są metody cyklu życia oraz jak przekazywać callbacki za pomocą props.

Podobał Ci się dzisiejszy artykuł? Udostępnij go w Twoich ulubionych mediach społecznościowych. Może znajdzie się ktoś, dla kogo również będzie wartościowy.

Bądź na bieżąco z serią Pierwszy projekt w ReactJS. Wystarczy polubić fanpage AlgoSmart na fejsie, obserwować mój profil na Twitterze i regularnie odwiedzać portal Polski Front-end.

Zdjęcie tytułowe autorstwa: unsplash-logoJoshua Sortino

Do zobaczenia za tydzień :).

Marcin Czarkowski

Cześć! Ja nazywam się Marcin Czarkowski, a to jest AlgoSmart - blog, na którym dzielę się wiedzą o ReactJS, JavaScript oraz CSS. Od niedawna tworzę materiały na YouTube, warto rzucić okiem :).