React komponente
Komponente su jedan od osnovnih koncepata Reacta i često se nazivaju "građevnim blokovima" React aplikacije. Najjednostavnije rečeno, komponente su pojedinačni elementi stranice. Kod "klasičnih" web stranica, izgled i struktura stranice bi se definirali kroz HTML strukturu, za svaku pojedinačnu stranicu - unutar <body> elementa bi definirali strukturu cijele aplikacije uz pomoć HTML-a te bi dodali oblikovanje (CSS) i eventualno mogućnost interakcije (JS). Često bi se u takvim situacijama javila potreba za ponovnim korištenjem dijela kôda što bi najčešće rezultiralo njegovim kopiranjem sa jedne stranice na drugu. Naravno, problem sa takvim pristupom je održavanje i izmjena dijelova stranice, koje sa porastom stranice postaje sve složenije.
Ideja React komponenti je kombinirati HTML, CSS i JavaScript u jednu logičku cjelinu (komponentu) koja se može koristiti više puta na različitim dijelovima stranice. Time se postiže jedna od glavnih principa React-a, a to je ponovno korištenje (eng. Reusability)
Korištenjem komponenti možemo korisničko sučelje (UI) "razbiti" na više neovisnih elemenata i svaki od tih elemenata promatrati kao izoliranu cjelinu.
Pisanje komponenti
Komponente u React-u možemo pisati na dva načina - u obliku funkcije ("Function Components" - funkcijske komponente) ili u obliku klase ("Class Components" - klasne komponente)
U primjeru ispod možete vidjeti jednostavnu React komponentu napisanu na oba načina:
function Pozdrav() {
return <h3>Pozdrav od React funkcijske komponente</h3>
}Iz perspektive samog React-a, ove dvije komponente su identične, tj. rezultiraju istim prikazom. U starijim verzijama React-a postojala je razlika između ove dvije vrste komponenti - funkcijske komponente su se koristile samo za prikaz statičkog sadržaja i u slučajevima kada komponenta nije trebala imati dodatnu unutarnju logiku. Zbog toga su se funkcijske komponente često nazivale prezentacijske ili čak "glupe" (eng. dumb) komponente. S druge strane, klasne komponente su se koristile za zahtjevnije zadatke jer su imale mogućnost pamćenja unutarnjih stanja i izvršavanja složenijih logičkih operacija (nazivale su se smart ili container komponentama).
Pojavom posebnih funkcija koje se nazivaju React Hooks, funkcijske komponente su dobile mogućnost pamćenja stanja i upravljanja životnim ciklusom komponente, te se izgubila podjela na "pametne/glupe" komponente. Dapače, zbog nešto jednostavnije sintakse, funkcijske komponente su postale standard te se u praksi koriste češće od klasnih komponenti. Zbog toga će i u ostatku tečaja svi primjeri biti navedeni u obliku funkcijskih komponenti, te ćemo se također posebno osvrnuti na React Hooks i njihovo korištenje.
Korištenje komponenti
Kroz jednostavni primjer ćemo demonstrirati pisanje nove komponente i njeno korištenje. U prethodno napravljenom početnom projektu, napravite novi direktorij "components", te u njemu datoteku "Pozdrav.jsx. Za početak možemo kopirati gornji primjer, samo nam još na kraju nedostaje naredba za izvoz (export) cijele funkcije kako bi je mogli uključiti u drugu datoteku.
function Pozdrav() {
return <h3>Pozdrav od React funkcijske komponente</h3>
}
export default PozdravNakon što smo definirali našu komponentu, možemo se vratiti u glavni dio aplikacije ("App.js") te unutar njega učitati našu funkcijsku komponentu. Ako smo ispravno naveli putanju do datoteke, možemo unutar App funkcije stvoriti jednu instancu naše komponente "Pozdrav".
import Pozdrav from "./components/Pozdrav";
import './App.css'
function App(){
const datum = new Date()
const a = 10
const b = 20
return (
<div>
<p>Dobar dan React, danas je {datum.toString()}</p>
<p className='izracun'>
{a} plus {b} je {a + b}
</p>
<Pozdrav />
</div>
)
}
export default AppVišestruko korištenje komponenti
Kao što je već prije spomenuto, glavna svrha komponenti je mogućnost ponovne upotrebe. Jednom kada smo definirali komponentu "Pozdrav", možemo je više puta iskoristiti, čak i na istoj stranici. Sve što je potrebno je napraviti novu instancu te komponente.
return (
<div>
<p>Dobar dan React, danas je {datum.toString()}</p>
<p className='izracun'>
{a} plus {b} je {a + b}
</p>
<Pozdrav />
<Pozdrav />
<Pozdrav />
</div>
)Odmah možemo uočiti prednost ovog pristupa sa komponentama. Na primjer, uočili smo neku pogrešku ili jednostavno želimo promijeniti izgled naše komponente za pozdrav. U tom slučaju je dovoljno otvoriti datoteku u kojoj smo definirali komponentu (njen funkcijski oblik) te napraviti željenu promjenu. Ta promjena će se odraziti na cijelu aplikaciju - gdje god smo koristili našu komponentu. Ovim pristupom smo izbjegli situaciju u kojoj moramo pretraživati cijelu aplikaciju kako bi pronašli dijelove kôda koje moramo ažurirati.
Postoji i jedan potencijalni problem - što ako želimo napraviti komponentu koja će većim dijelom biti identična ali će se razlikovati po sadržaju. Na primjer, bilo bi dobro kada bi naša komponenta mogla uz pozdrav ispisivati ime korisnika - ali to ime mora biti promjenjivo inače bi morali raditi komponentu za svako moguće ime, čime bi izgubili smisao ponovnog korištenja. Kako bi se povećala mogućnost ponovnog korištenja komponenti, potrebno je omogućiti mogućnost njihove prilagodbe. Konkretno, naša prethodno napravljena komponenta za pozdrav bi bila puno korisnija kada bi mogli uz pozdrav ispisati ime korisnika kojeg želimo pozdraviti - naravno pri tome ime mora biti prilagodljivo.
Srećom, React komponente imaju mogućnost slanja i primanja parametara čime postaju još korisnije.
React svojstva (props)
Slanje (i primanje parametara) se u Reactu postiže korištenjem tzv. "props" (skraćeno od properties). Glavni zadatak props-a je slanje podataka iz jedne komponente u drugu i možemo reći da služe kao glavni komunikacijski kanal između komponenti. Glavne karakteristike props-a su:
- mogu se koristiti u funkcijskim i klasnim komponentama
- svojstva (props) se šalju od vrha prema dolje tj. roditeljske (parent) komponente šalju podatke komponentama potomcima (child)
- props se mogu samo čitati (read-only). Jednom kada komponenta primi podatke, ne može ih modificirati već samo koristiti ili proslijediti dalje (svojim child komponentama).
Korištenje props-a
Pogledajmo na konkretnom primjeru kako koristimo React props. Iskoristiti ćemo prethodno napravljenu komponentu Pozdrav i prilagoditi je tako da može primati props.
function Pozdrav(props) {
return (
<>
<h3>Pozdrav od React funkcijske komponente</h3>
<p>Dobar dan, {props.ime}</p>
</>
)
}Obratite pozornost na dvije stvari u prethodnom primjeru. U potpis funkcije smo dodali ulazni argument props i tako naznačili da naša komponenta prima neke podatke. Zatim smo u tijelu komponente iskoristili svojstvo props.ime tj. pristupili smo podacima koje smo dobili.
Sada nam još preostaje vratiti se na "glavni" dio aplikacije, tamo gdje koristimo ovu komponentu, i poslati odgovarajuće podatke koji su potrebni za ispravan prikaz komponente:
return (
<div>
<p>Dobar dan React, danas je {datum.toString()}</p>
<p className='izracun'>
{a} plus {b} je {a + b}
</p>
<Pozdrav ime="Matea" />
</div>
)Spremite promjene i pogledajte rezultat u pregledniku. Komponenta bi trebala ispravno prikazati primljeni parametar - ime osobe koju želimo pozdraviti. Sada možemo ovu komponentu koristiti svaki put kada želimo ispisati pozdrav, a pri tome imamo mogućnost promjene imena.
<Pozdrav ime="Matea" />
<Pozdrav ime="Ivan" />
<Pozdrav ime="Maja" />Kao što je vidljivo iz primjera, props se koriste na sličan način kao i atributi unutar HTML oznaka. Unutar oznake za komponentu navodimo ime svojstva i pripadajuću vrijednost. Naravno, nismo ograničeni na jedno svojstvo, pa komponenti možemo poslati više podataka.
return (
<div>
<p>Dobar dan React, danas je {datum.toString()}</p>
<p className='izracun'>
{a} plus {b} je {a + b}
</p>
<Pozdrav ime="Matea" god={27} />
</div>
)Ne zaboravite prilagoditi komponentu i prikazati nove podatke:
function Pozdrav(props) {
return (
<>
<h3>Pozdrav od React funkcijske komponente</h3>
<p>Dobar dan, {props.ime}</p>
<p>Imaš {props.god} godina.</p>
</>
)
}Uočite razliku u sintaksi kada je vrijednost svojstva u tekstualnom obliku (string) i kada je vrijednost svojstva broj. Zapravo bi u oba slučaja trebali koristiti { } zagrade ali kada je u pitanju string React nam dozvoljava skraćeni zapis. Komponentu smo mogli instancirati i na ovaj način sa identičnim rezultatom:
<Pozdrav ime={"Matea"} god={27} />Možemo pogledati još jedan primjer slanja podataka kroz props. U ovom primjeru kao dvije vrijednosti koristimo internu varijablu i aritmetičku operaciju:
function App(){
const osoba = "Ivan";
return (
<div>
<Pozdrav ime={"Matea"} god={27} />
<Pozdrav ime={osoba} god={10 + 18} />
</div>
)
}children svojstvo
Svi dosadašnji primjeri su sadržavali komponente samozatvarajućeg oblika - npr. <Pozdrav />. Iako ta komponenta unutar sebe može sadržavati druge (ugniježđene) komponente, u samoj komponenti je točno definirano što će se u njoj nalaziti. Ponekad ćemo imati potrebu napraviti komponentu unutar koje želimo ugnijezditi različite elemente ili nam neće biti unaprijed poznato što će se nalaziti unutar nje. Jedan od primjera može biti komponenta koja sadrži <div> element sa nekim zadanim oblikovanjem - npr. zaobljeni okvir fiksnih dimenzija unutar kojeg želimo prikazati različiti sadržaj.
U takvim slučajevima unutar komponente koristimo ugrađeno svojstvo props.children koje predstavlja ugniježđene elemente naše komponente bez da unaprijed znamo koji su. Pogledajmo primjer - komponentu možemo definirati ovako:
function Pozdrav(props) {
return (
<>
<h3>Pozdrav od React funkcijske komponente</h3>
<p>Ispod se prikazuje ugniježđeni sadržaj</p>
{props.children}
</>
)
}I sada možemo koristiti tu komponentu, ali na način da ima otvarajuću i zatvarajuću oznaku te da se između njih mogu nalaziti child elementi.
function App(){
return (
<div>
<Pozdrav>
<p>Ovo je child element komponente</p>
</Pozdrav>
</div>
)
}Na ovaj način možemo unutar komponente "ubaciti" koji god sadržaj želimo i on će se prikazati umjesto {props.children} izraza. Kao što vidite na primjeru, nije potrebno ugniježđeni sadržaj posebno označavati kao children svojstvo, to React radi automatski.
Pomoćne funkcije
React komponente također mogu sadržavati pomoćne funkcije. Nadograditi ćemo prethodni primjer te u komponentu dodati funkciju za računanje godine rođenja.
function Pozdrav(props) {
const godRod = () => {
let rez = new Date().getFullYear() - props.god
return rez
}
return (
<>
<h3>Pozdrav od React funkcijske komponente</h3>
<p>Dobar dan, {props.ime}</p>
<p>Imaš {props.god} godina.</p>
<p>Vjerojatno si rođen(a) {godRod()}. godine</p>
</>
)
}
export default PozdravLogiku izračuna godine rođenja smo izdvojili u posebnu funkciju koja se nalazi unutar same komponente - kao i njen poziv. Uočite da pomoćnoj funkciji nije potrebno slati godinu kao argument jer se ona nalazi unutar komponente i ima pristup props objektu.
Destrukturiranje
U slučajevima kada više puta unutar komponente pristupamo nekoj vrijednosti iz props objekta (ili kad imamo više podataka), stalno pisanje props.* može postati naporno i umanjuje čitljivost kôda. Taj problem se može riješiti korištenjem mogućnosti koju nam pruža JS sintaksa - a to je destrukturiranje.
Destrukturiranje nam omogućava "izvlačenje" vrijednosti iz props objekta i njihovo korištenje unutar komponente u skraćenom zapisu. Pogledajmo prethodni primjer sa destrukturiranim props objektom:
function Pozdrav({ime, god}) {
const godRod = () => {
let rez = new Date().getFullYear() - god
return rez
}
return (
<>
<h3>Pozdrav od React funkcijske komponente</h3>
<p>Dobar dan, {ime}</p>
<p>Imaš {god} godina.</p>
<p>Vjerojatno si rođen(a) {godRod()}. godine</p>
</>
)
}
export default PozdravNakon što smo destrukturirali props objekt i iz njega "izvukli" argumente ime i god, možemo ih koristiti kao lokalne varijable unutar komponente. Ukratko, sa destrukturiranjem smo postigli isti učinak kao da smo sami na početku komponente deklarirali dvije lokalne varijable i u njih spremili vrijednosti iz props objekta.
Možemo vidjeti i takav primjer, samo da bi dobili dojam postignutog učinka:
function Pozdrav(props) {
const ime = props.ime
const god = props.god
const godRod = () => {
let rez = new Date().getFullYear() - god
return rez
}
return (
<>
<h3>Pozdrav od React funkcijske komponente</h3>
<p>Dobar dan, {ime}</p>
<p>Imaš {god} godina.</p>
<p>Vjerojatno si rođen(a) {godRod()}. godine</p>
</>
)
}
export default Pozdrav