Te tajemnicze obiekty nazywane są elementami React. Ich zadaniem jest opisywanie instancji komponentu lub węzła DOM wraz z ich wszystkimi właściwościami. Element zawiera informacje o typie, właściwościach i dzieciach opisywanej struktury.
Dzięki elementom React, biblioteka otrzymuje informacje o tym, co chcemy zobaczyć na ekranie.
Element React zaledwie opisuje węzeł/instancje komponentu, nie należy go z nimi utożsamiać. To częsta pomyłka.
Zorganizowanym połączeniem wszystkich elementów React, z których składa się aplikacja, jest właśnie jej wirtualny DOM.
Zawartość wirtualnego DOM jest porównywana z rzeczywistym DOM za pomocą specjalnego algorytmu. Dopiero w oparciu o wyniki tego porównania biblioteka aktualizuje stan aplikacji w optymalny sposób bez bezpośredniego udziału programisty.
Jak powstają elementy React?
Mamy dwie możliwości tworzenia elementów React: czysty JS oraz JSX.
Zacznę od pierwszego rozwiązania. Jego analiza dostarczy dużo informacji o tym, jak biblioteka radzi sobie z wyświetlaniem elementów.
Metoda, która pozwala na utworzenie nowego elementu to React.createElement(type, props, children)
.
Omówmy parametry przekazywane do metody:
type(string || React.createClass())
– mamy dwie możliwości: string reprezentujący element HTML (np. ‚div’) lub instancję komponentu React.props (null || object)
– obiekt zawierający dane przekazywane do elementu/komponentu wskazanego w pierwszym parametrze. Jeżeli tworzymy węzeł DOM, a nazwa właściwościprops
jest poprawnym atrybutem HTML, to zostanie ona przypisana bezpośrednio do wygenerowanego elementu. W przypadku komponentów, obiektprops
jest przekazywany do nich, jako parametr (komponenty funkcyjne) lub właściwość (komponenty klasowe). Więcej na temat komponentów i props w kolejnym wpisie.children(null || string || React.createClass() || React.createElement)
– ostatni parametr decyduje o zawartości elementu. Do wyboru, do koloru: zwykły tekst, instancja komponentu React lub kolejny element React.
Zobaczmy, jak to wygląda w praktyce:
Ta linijka kodu pozwoliła na utworzenie elementu React i przypisanie go do zmiennej bitCoinEl
.
Renderowanie elementów w DOM
Aby dodać ten element do wirtualnego DOM, musimy przekazać go do odpowiedniej metody. Jest nią ReactDOM.render(reactEl, domNode)
.
ReactDOM
to biblioteka zarządzająca wyświetlaniem elementów w DOM.
Omówmy parametry metody render()
:
reactEl
– element, który chcemy wyświetlić w rzeczywistym DOM.domNode
– węzeł, w którym chcemy zagnieździćreactEl
.
Przykładowe użycie:
Powyższe wywołanie metody wiąże się z:
- Utworzeniem elementu
bitCoinEl
w wirtualny DOM. - Porównaniem wirtualnego i rzeczywistego DOM przez
ReactDOM
. - Pojednaniem drzew (ang. tree reconciliation), czyli wprowadzeniem minimalnej ilości aktualizacji, która pozwali usunąć różnice pomiędzy wirtualnym a rzeczywistym DOM.
Pewnie zdążyłeś zadać sobie pytanie, jak to wszystko miałoby pozwolić na stworzenie aplikacji rozmiarów Facebooka.
Rzecz w tym, że w React.createElement()
możemy zagnieździć dowolną ilość elementów-dzieci.
Do ReactDOM.render()
przekazuje się wyłącznie nadrzędny element.
Jak to wygląda w praktyce, możesz zaobserwować w pliku index.js
każdego projektu wygenerowanego przez create-react-app.
Okej, co z tego, że się da, skoro nawet stworzenie prostej listy wiązało się z naprodukowaniem dużej ilości kodu. Przecież to nadal coś skromnego przy zaplanowanych przeze mnie komponentach Coin
czy SearchBar
.
JSX
Na szczęście mamy do dyspozycji zgrabniejszy sposób na tworzenie elementów React.
To, co widzisz powyżej, to drugi bohater dzisiejszego wpisu czyli JSX (JavaScript XML). JSX jest nakładką składniową rozszerzająca ECMAScript. Do złudzenia przypomina HTML, ale wbrew pozorom, nadal mamy do czynienia z JS. Składnia ta jest przetwarzana przez odpowiednio skonfigurowany transpilator Babel. Finalnie, do naszej przeglądarki trafi nic innego jak mnóstwo wywołań React.createElement()
.
JSX rozszerza nasze możliwości, łącząc HTML z JavaScript. W odróżnieniu od rozwiązań znanych z Vue i Angulara, nie umieszczamy JS w HTML, tylko dokładnie na odwrót – HTML w JS.
Skąd taki pomysł? Oto wyjaśnienie programistów React:
[…] znaczniki oraz kod, który je generuje, są ze sobą ściśle powiązane. Ponadto, logika wyświetlania jest często bardzo skomplikowana i korzystanie z języków szablonowych, aby ją wyrazić, staje się niewygodne. Doszliśmy do wniosku, że najlepszym rozwiązaniem jest generowanie HTML i drzew komponentów bezpośrednio z kodu JavaScript, tak abyście mogli wykorzystywać całą ekspresyjną moc prawdziwego języka programowania do budowania interfejsu użytkownika.
Wracając do przykładu listy w JSX. Babel przetworzy go do następującego kodu JavaScript:
7 linijek vs 31 linijek. Jak widzisz, JSX to dużo wygodniejszy sposób niż bezpośrednie wywoływanie React.createElement()
. Na początku składnia może wydawać Ci się dziwna. Ba, jeżeli jesteś front-endowym purystą, to pewnie doprowadza Cię do gorączki. Mogę zapewnić, że jest to przyjemne rozwiązanie, do którego przyzwyczaisz się w ciągu kilku godzin. Jest czytelniejsze, niż wielkie piramidy, na które skazuje nas React.createElement()
. Jeżeli mimo to bardziej cenisz sobie oddzielanie HTML od JS niż komfort pracy… to możesz zadowolić się bezpośrednim wywoływaniem React.createElement()
. Święte wojny w tej sprawie toczą się od kilku lat, nie mam zamiaru się w nie angażować. Podobnie jak 99% programistów, będę korzystał z JSX. Najwyżej spłonę na inkwizycyjnym stosie ;).
Sam proces renderowania elementów stworzonych za pomocą JSX, przebiega dokładnie w ten sam sposób, co w przypadku bezpośrednich wywołań React.createElement()
.
Wyrażenia JavaScript w JSX
JSX jest tylko nakładką językową, więc możemy bezproblemowo łączyć kod znaczników z wyrażeniami JavaScript.
Wywołania funkcji, destrukturyzacja, operacje matematyczne – to najpopularniejsze sposoby wykorzystywania wyrażeń JS wewnątrz HTML.
Aby wprowadzić wyrażenie do JSX, wystarczy otoczyć je pomocą nawiasów klamrowych {}
. Wtedy silnik Reacta obliczy jego wartość i przekaże wynik do renderowanego elementu.
Znasz już działanie kluczowej dla ReactaJS metody React.createElement()
oraz nakładki językowej JSX, zapewniającej dodatkową warstwę abstrakcji, która ułatwi Ci codzienną pracę.
Tworzymy pierwszy komponent
Zróbmy użytek z nabytej przez Ciebie wiedzy i przejdźmy do stworzenia prototypu pierwszego komponentu.
W szczegóły dotyczące komponentów: ich budowę, działanie, podział na kategorie, będę wdawał się w kilku kolejnych wpisach. Póki co zadowolimy się praktyczną stroną deklarowania komponentów.
Poniższe instrukcje dotyczą stanu projektu z tego commita.
Jeżeli nie zrobiłeś tego wcześniej, utwórz plik Coin.js
pod ścieżką /src/components/CoinList/Coin/
.
Zanim zaczniesz pisać kod w JSX, musisz zaimportować moduł Reacta do przestrzeni pliku (więcej informacji o importowaniu modułów znajdziesz w tym wpisie).
Następnie utworzymy strzałkowe wyrażenie funkcyjne przypisane do zmiennej const Coin
. Zawsze nazywaj zmienne/klasy komponentów tak samo jak zawierające je pliki i foldery. To jeden z konwenansów stosowanych przez większość React developerów.
Każdy komponent powinien zwracać JSX (w innym przypadku zostanie wyrzucony błąd), więc dodamy do naszego wyrażenia return
z najzwyklejszym divem.
Ostatnia rzecz, o której musisz pamiętać, to domyślny eksport komponentu.
Zanim przejdziemy dalej, wykorzystajmy okazję do zredukowania liczby linijek kodu. Dopóki jedynym zadaniem komponentu jest zwracanie JSX, to możemy zrezygnować z nawiasów klamrowych i słowa kluczowego return
. Jest to związane z własnościami funkcji strzałkowych, kod JSX jest traktowany jako pojedyncze wyrażenie.
Aby komponent został wyrenderowany w DOM, musisz utworzyć instancję w komponencie nadrzędnym App. Nie zapomnij o uprzednim zaimportowaniu Coin
do App.js
Jeżeli uruchomisz aplikację, to będziesz mógł podziwiać przepięknie biały ekran. To nie błąd, po prostu komponent Coin nie zwraca nic poza pustym divem.
Zanim przejdziesz do dodawania zawartości, zrób sobie mały eksperyment. Spróbuj dodać drugiego pustego diva.
Taka składnia prowadzi do wyrzucenia błędu. Każdy komponent może zwracać wyłącznie jeden element nadrzędny. Takie ograniczenie wynika z działania metody React.createElement()
. Zerknij na jej pierwszy parametr i wszystko będzie jasne.
Okej, dodaj trochę treści do komponentu.
Założę się, że zastanawia Cię atrybut className
użyty w elementach ul
i li
. To zaledwie nowe szaty dla starego dobrego atrybutu class
. Skąd takie kombinacje? Tak się składa, że class
to również słowo kluczowe w vanilla JS, co wymusiło użycie alternatywnej nazwy dla atrybutu przydzielającego style do znaczników.
Właśnie stworzyłeś swój pierwszy komponent. Nie imponuje on zawartością, walorami estetycznymi ani wszechstronnością. Popracujemy nad tym w dwóch kolejnych wpisach.
Zadanie domowe
Stwórz prototypu komponentu Header
w pliku Header.js
. Jego zawartość powinna składać się z nagłówka tytułowego oraz paragrafu informującego o aktualnej kapitalizacji giełdy (ang. market cap). Następnie stwórz instancję Header w komponencie nadrzędnym aplikacji.
Rozwiązanie znajdziesz tutaj.