SVEUČILIŠTE U RIJECI
TEHNIČKI FAKULTET
Preddiplomski sveučilišni studij računarstva
Završni rad
KLIJENTSKI DIO JEDNOSTRANIČNE WEB APLIKACIJE
Rijeka, rujan 2016.
Tina Gojak
0069064669
SVEUČILIŠTE U RIJECI
TEHNIČKI FAKULTET
Preddiplomski sveučilišni studij računarstva
Završni rad
KLIJENTSKI DIO JEDNOSTRANIČNE WEB APLIKACIJE
Mentor: izv. prof. dr. sc. Miroslav Joler
Rijeka, rujan 2016.
Tina Gojak
0069064669
ZADATAK
IZJAVA O SAMOSTALNOJ IZRADI RADA
Izjavljujem da sam samostalno izradila ovaj rad.
Rijeka, rujan 2016. ___________________
Tina Gojak
Zahvala:
Zahvaljujem svima koji su mi svojim savjetima, preporukama i podrškom pomogli
tijekom studija i pri izradi ovog završnog rada.
Posebna zahvala dr. sc. Damiru Arbuli na vođenju i asistenciji tijekom izrade rada
te brzom odgovaranju na pozive u pomoć.
Također, kolegi i prijatelju Leu Brdaru zahvaljujem na suradnji i strpljenju na
našim brojnim zajedničkim projektima.
SADRŽAJ
Sadržaj ............................................................................................................................................ 6
1. UVOD ..................................................................................................................................... 1
1.1. Problem i predmet istraživanja ......................................................................................... 1
1.2. Struktura rada ................................................................................................................... 1
2. JEDNOSTRANIČNE APLIKACIJE ...................................................................................... 2
2.1. Tehnički pristup ................................................................................................................ 4
2.1.1. Arhitektura poslužitelja ............................................................................................. 4
2.1.2. JavaScript .................................................................................................................. 4
2.1.3. JSX ............................................................................................................................ 5
2.1.4. AJAX ......................................................................................................................... 6
2.1.5. XMLHttpRequest ...................................................................................................... 6
3. REACT.JS KNJIŽNICA ZA RAZVOJ JEDNOSTRANIČNIH APLIKACIJA .................... 7
3.1. Ključna svojstva ............................................................................................................... 8
3.1.1. Jednosmjeran tok podataka ....................................................................................... 8
3.1.2. Virtualni DOM .......................................................................................................... 8
3.2. React komponente ............................................................................................................ 9
3.3. React DevTools .............................................................................................................. 11
4. REDUX KNJIŽNICA ZA UPRAVLJANJE STANJEM JEDNOSTRANIČNE .....................
APLIKACIJE ....................................................................................................................... 13
4.1. Redux elementi ............................................................................................................... 13
4.1.1. Akcije ...................................................................................................................... 13
4.1.2. Kreatori akcija ......................................................................................................... 14
4.1.3. Reduktori ................................................................................................................. 14
4.1.4. Spremište ................................................................................................................. 15
4.2. Tok podataka .................................................................................................................. 15
4.3. Redux DevTools ............................................................................................................. 16
4.4. Povezivanje Reduxa s Reactom ...................................................................................... 18
5. DODATNI ALATI ZA RAZVOJ JEDNOSTRANIČNE APLIKACIJE ............................. 21
5.1. npm alat za upravljanje JavaScript programskim paketima ........................................... 21
5.2. Webpack module bundler ............................................................................................... 21
5.3. Babel ............................................................................................................................... 22
6. DJANGO ONLINE JUDGE ................................................................................................. 24
6.1. Postavljanje okruženja .................................................................................................... 24
6.2. Struktura aplikacije ......................................................................................................... 25
6.3. Akcije i tok podataka ...................................................................................................... 30
6.4. Autentikacija korisnika ................................................................................................... 33
7. PREDNOSTI I NEDOSTACI PREBACIVANJA APLIKACIJSKE LOGIKE ......................
NA KLIJENTA ..................................................................................................................... 37
7.1. Prednosti ......................................................................................................................... 37
7.2. Nedostaci ........................................................................................................................ 38
7.3. Zaključak analize ............................................................................................................ 39
ZAKLJUČAK ............................................................................................................................... 40
LITERATURA I IZVORI ............................................................................................................. 41
SAŽETAK ..................................................................................................................................... 42
Abstract ......................................................................................................................................... 42
1
1. UVOD
1.1.Problem i predmet istraživanja
Model poslužitelja i klijenta je raširena i uvriježena struktura aplikacija koja raspodjeljuje
zadatke i teret između pružatelja resursa ili usluga – poslužitelja (engl. server) i potražitelja –
klijenta (engl. client). Klijent i poslužitelj komuniciraju putem mreže tako da poslužitelj dijeli
svoje resurse s klijentom, a klijent zahtijeva resurse ili usluge od poslužitelja.
U modernim jednostraničnim web aplikacijama tradicionalna arhitektura klijenta i
poslužitelja izmijenjena je na način da klijent preuzima puno više odgovornosti. Većina (ili sva)
aplikacijska logika prebačena je na klijenta, što poslužitelja svodi na jednostavno sučelje za dohvat
podataka.
Cilj ovog završnog rada je izrada klijentskog dijela jednostranične web aplikacije Django
Online Judge koristeći React.js radni okvir te analiza prednosti i nedostataka prebacivanja
aplikacijske logike na klijenta.
1.2. Struktura rada
Rad se sastoji od 7 poglavlja, od kojih prvih 5 služe kao teorijski pregled i uvod u
tehnologije i alate koji se koriste u praktičnom dijelu završnog rada. U šestom poglavlju opisana
je aplikacija izrađena kao praktični dio rada, a sedmo, zaključno poglavlje navodi prednosti i
nedostatke korištenog pristupa izradi web aplikacije.
2
2. JEDNOSTRANIČNE APLIKACIJE
Jednostranične aplikacije (Single Page Application – SPA) su web aplikacije ili web
stranice koje nastoje korisniku pružiti iskustvo slično desktop aplikacijama. Za razliku od
tradicionalnih web aplikacija u kojima se svakim zahtjevom prema poslužitelju stvara i dostavlja
u obliku odgovora nova HTML stranica, u SPA se HTML stranica koja sadrži aplikaciju učitava
samo jednom, prilikom inicijalnog pokretanja aplikacije, a sve daljnje promjene sučelja, kao
odgovor na korisnikove radnje, su parcijalne. Aplikacijska logika uglavnom je prebačena na
klijentsku stranu, što omogućuje brze male promjene stanja, budući da svaka promjena ne
zahtijeva dohvaćanje podataka s poslužitelja. Također, ubrzava interakciju s komponentama
stranice koje utječu na samo mali dio stranice (npr. otvaranje izbornika, odabir iz izbornika...) i ne
mapiraju se u URL-ove (engl. Uniform Resource Locator – web adresa). Za takvu responzivnost
aplikacije zaslužni su AJAX (engl. Asynchronous JavaScript and XML – asinkroni JavaScript i
XML) i HTML5 (engl. Hyper Text Markup Language 5 – peta verzija jezika korištenog za pisanje
web stranica), kao i JavaScript s klijentske strane. Svakom promjenom aplikacija dolazi u novo
stanje koje se može usporediti s web stranicom (engl. webpage) na tradicionalnom web sjedištu
(engl. website) te je moguće navigirati između stanja.
Na slici 2.1. ilustrirana je razlika u komunikaciji s poslužiteljem kod tradicionalnih web
aplikacija i SPA. Kod tradicionalnih stranica, nakon svakog poziva aplikacije poslužitelju, on
odgovara novom HTML stranicom, što izaziva osvježenje (engl. refresh) stranice u pregledniku.
Kod SPA, nakon što se aplikacija prvi put učita, sva se komunikacija odvija kroz AJAX pozive,
na koje poslužitelj odgovara uglavnom u JSON (engl. JavaScript Object Notation) ili XML (engl.
Extensible Markup Language) formatu. Zatim aplikacija koristi dobivene podatke za dinamičko
ažuriranje stranice, bez njenog ponovnog učitavanja. [1]
3
Slika 2.1. Životni ciklus web aplikacije
Osim responzivnosti aplikacije, odnosno kraćih i time bržih odgovora, prednost SPA je
odvajanje aplikacijske (AJAX zahtjev i JSON odgovor) i prezentacijske (HTML markup) logike.
Time je olakšano projektiranje i održavanje svakog sloja za sebe, s budući da je u dobro
projektiranoj SPA moguće promijeniti HTML markup bez promjene koda koji implementira
aplikacijsku logiku.
U čistoj SPA sva se interakcija s korisničkim sučeljem odvija isključivo na klijentu. Sučelje
je implementirano koristeći standardne web tehnologije kao što su JavaScript, HTML i CSS.
Nakon početnog učitavanja stranice, poslužitelj služi isključivo kao uslužni sloj, a klijent treba
znati samo koje HTTP zahtjeve treba slati, bez ikakvog znanja kako poslužitelj implementira
dohvat i održavanje podataka odnosno resursa.
4
2.1. Tehnički pristup
U ovom će se poglavlju opisati tehnike koje pregledniku omogućuju održavanje
jedinstvene stranice, čak i kada je potrebna komunikacija s poslužiteljem.
2.1.1. Arhitektura poslužitelja
Komunikacija s poslužiteljem se odvija u pozadini, a s obzirom na ulogu poslužitelja u
aplikaciji, razlikujemo tri vrste SPA [2]:
Arhitektura "tankog" poslužitelja (Thin server architecture) - aplikacijska logika u
potpunosti je prebačena na klijenta, čime je uloga poslužitelja svedena na jednostavno
sučelje za dohvat podataka. Tako se nastoji smanjiti kompleksnost sustava u cjelini.
Ovakve se aplikacije smatraju „čistim“ jednostraničnim aplikacijama.
Arhitektura "debelog" poslužitelja s očuvanjem stanja (Thick stateful server architecture)
- poslužitelj čuva potrebna stanja u memoriji klijenta i kada dobije zahtjev, pošalje
odgovarajući odgovor koji ažurira stanje klijenta, a u isto vrijeme se ažurira i stanje na
poslužitelju. Većina aplikacijske logike izvodi se na poslužitelju, što znači da je
poslužitelju potrebno više memorije, ali konačna aplikacija je pojednostavljena jer su
podaci i stanje korisničkog sučelja spremljeni na isto mjesto u memoriji poslužitelja, bez
potrebe za dodatnom komunikacijom između klijenta i poslužitelja.
Arhitektura "debelog" poslužitelja bez očuvanja stanja (Thick stateless server architecture)
- u ovoj varijanti "debelog" poslužitelja, poslužitelj ne čuva stanje klijenta, već mu klijent
šalje svoje stanje, uglavnom AJAX zahtjevom. Zatim poslužitelj rekonstruira stanje dijela
stranice koji treba ažurirati i vraća klijentu odgovarajući kod koji će ga dovesti u novo
stanje. Ovim pristupom je potrebno poslužitelju slati više podataka i može zahtijevati više
resursa za obradu zahtjeva, ali budući da se na poslužitelju ne čuvaju specifični podaci za
svakog klijenta, AJAX zahtjevi mogu biti poslani na više čvorova bez potrebe za sesijama.
2.1.2. JavaScript
JavaScript (JS) je objektno-orijentirani programski jezik, najpoznatiji kao skriptni jezik za
razvoj web aplikacija, iako se koristi i u drugim okruženjima. Izvodi se na klijentskoj strani, u
5
interpreteru ugrađenom u web preglednik i uglavnom kontrolira kako će stranica, odnosno
korisničko sučelje reagirati na određeni događaj.
Standard na kojem je baziran naziva se ECMAScript. Od 2012., svi moderni web
preglednici u potpunosti podržavaju ECMAScript 5.1., a od 2015. se primjenjuje ECMAScript 6
ili ES6, koji je trenutno prihvaćen u razvoju jednostraničnih aplikacija, jer ima mnoge korisne
značajke koje olakšavaju i ubrzavaju pisanje koda.
Razvijeno je više JavaScript radnih okvira koji podržavaju razvoj jednostraničnih
aplikacija. Najrašireniji su AngularJS, Ember.js, Meteor.js i React.js. U praktičnom dijelu ovog
završnog rada korišten je React.js radni okvir te je detaljno opisan u poglavlju React.
2.1.3. JSX
JSX (engl. Java Serialization to XML) je predprocesor koji dodaje XML sintaksu u
JavaScript. Uobičajeno ga je koristiti pri razvoju React aplikacija, iako je moguće koristiti i čisti
JavaScript. JSX pruža jednostavnost i eleganciju React kodu, jer nudi poznatu i sažetu sintaksu za
kreiranje strukture stabla, koja se koristi u Reactu. Također, prilikom prevođenja u JavaScript
provodi se optimizacija koja osigurava da se nastali kod pokreće brže nego što bi se pokretao
ekvivalentni kod napisan direktno u JavaScriptu.
Na nastavku je prikazan React element HelloMessage napisan u JSX-u, a zatim njegov
ekvivalent u JavaScriptu.
// hello.jsx
var HelloMessage = React.createClass({
render: function() {
return <div>Hello World!</div>
}
})
// hello.js
var HelloMessage = React.createClass({
displayName: "HelloMessage",
render: function() {
return React.createElement("div", null, "Hello World!")
}
})
6
2.1.4. AJAX
AJAX, skraćeno za Asynchronous JavaScript and XML, je skup tehnika korištenih od
strane klijenta pri razvoju asinkronih web aplikacija. Omogućuje aplikacijama da asinkrono
dohvaćaju podatke s poslužitelja u pozadini, bez utjecaja na trenutni izgled i ponašanje stranice,
pri čemu koristi poseban oblik zahtjeva imena XMLHttpRequest. AJAX odvaja prezentacijski sloj
od sloja za razmjenu podataka kako bi se web stranice mogle mijenjati dinamički, odnosno bez
potrebe za ponovnim učitavanjem cijele stranice.
2.1.5. XMLHttpRequest
XMLHttpRequest ili kraće XHR je aplikacijsko programsko sučelje (engl. Application
Programming Interface – API) u formi objekta čije metode prenose podatke između web
preglednika i web poslužitelja. Zahtjev se odvija u pozadini (asinkron je) i uglavnom vraća XML
ili JSON podatke pomoću kojih se izmjenjuju dijelovi stranice, bez potrebe za osvježenjem cijele
stranice.
7
3. REACT.JS KNJIŽNICA ZA RAZVOJ JEDNOSTRANIČNIH
APLIKACIJA
React (React.js ili ReactJS) je JavaScript knjižnica otvorenog koda (open-source) koja nudi
pregled podataka koji se generiraju kao HTML. Specifičnosti Reacta su:
- pisanje komponenti koje se prikazuju kao posebne HTML oznake (tags),
- "nizvodni tok podataka" (engl. "data flows down"), odnosno nemogućnost komponenti
da izmijene svoje nadkomponente,
- jasno razdvajanje komponenti koje je vrlo korisno za razvoj modernih jednostraničnih
aplikacija (SPA), budući da je glavna ideja da komponente budu ponovno iskoristive u
različitim aplikacijama, i kada se podaci koje prikazuju promijene.
Tradicionalno, korisnička sučelja web aplikacija grade se pomoću HTML predložaka
(engl. templates), koji definiraju sve mogućnosti koje se nude za izradu korisničkog sučelja. React
za izgradnju komponenti koristi pravi i potpuni programski jezik, JavaScript, koji ima nekoliko
prednosti nad korištenjem predložaka. JavaScript je fleksibilan i moćan programski jezik koji ima
mogućnost izgradnje apstraktnih klasa, što je od izuzetne važnosti pri izradi velikih aplikacija, a
sjedinjenje prezentacijskih dijelova s odgovarajućom logikom olakšava održavanje i proširenje
React koda.
Prednosti Reacta najviše se ističu kada se podaci koje prikazuje mijenjaju tijekom vremena.
U tradicionalnim JavaScript aplikacijama, nakon svake promjene podataka potrebno je napraviti
odgovarajuće promjene u DOM-u. React eliminira potrebu za ručnim izmjenama. Kada se
komponenta prvi put inicijalizira, poziva se render metoda koja generira jedan prikaz. Kada se
podaci promijene, ponovno se poziva render metoda, ali React uspoređuje prethodni poziv s
trenutnim i generira minimalni set promjena koje će se primijeniti na DOM. Ovaj se proces naziva
reconciliation. Budući da je takvo generiranje jako brzo, nije potrebno eksplicitno definirati
podatkovno uparivanje (engl. data bindings - procesi koji povezuju korisničko sučelje s
aplikacijskom logikom). [3]
8
3.1. Ključna svojstva
3.1.1. Jednosmjeran tok podataka
Komponentama su u oznakama (HTML tag) proslijeđena svojstva (engl. propertises,
skraćeno props), skup nepromjenjivih vrijednosti koje komponenta može koristiti. Iako ih ne može
direktno mijenjati, može poslati funkciju povratnog poziva (engl. callback function) koja može
promijeniti vrijednosti. Mehanizam se opisuje kao jednosmjeran tok podataka, ili "properties flow
down; actions flow up"; svojstva se prosljeđuju isključivo niz hijerarhijsko stablo komponenata, a
akcije se prosljeđuju uz stablo.
Slika 3.1. Jednosmjeran tok podataka u React aplikaciji
3.1.2. Virtualni DOM
Objektni model dokumenta (engl. Document Object Model - DOM) je sučelje za dohvat i
modifikaciju XML/HTML informacija određene stranice, a ima oblik strukturnog stabla
dokumenata. Kada se želi dinamički promijeniti sadržaj web stranice, mijenja se DOM.
Struktura stabla omogućuje lako kretanje do čvorova, ali ne nužno i brzo, budući da DOM
stabla mogu biti golema, a SPA zahtijevaju često i neprestano mijenjanje DOM stabla. React
olakšava posao na dva načina:
Deklarativnost – umjesto ručnog prolaženja kroz DOM stablo, dovoljno je deklarirati kako
bi komponenta trebala izgledati, a React u pozadini odrađuje posao, koristeći HTML DOM
API metode
Virtualni DOM (Virtual DOM)
9
Virtualni DOM je apstrakcija HTML DOM-a, odvojena od implementacijskih detalja
specifičnih za web preglednik. Može ga se promatrati kao Reactovu lokalnu, pojednostavljenu
verziju HTML DOM-a, a omogućuje Reactu brzinu unutar tog apstraktnog sloja, bez potrebe za
"pravim" DOM operacijama, koje su često spore i ovise o web pregledniku. Kod se zatim može
pisati kao da se cijela stranica učitava pri svakoj promjeni, a React zapravo iščita što se zaista
promijenilo i generira samo te komponente. Cijena implementacije virtualnog DOM-a je veličina
aplikacije. Postoje alati koji je mogu minimizirati, ali pritom oduzimaju neke funkcionalnosti. [4]
3.2. REACT KOMPONENTE
Ideja Reacta je da se aplikacija razloži u više nezavisnih komponenata. Idealno, svaka bi
komponenta trebala odrađivati samo jednu zadaću. Ako naraste, trebala bi se razložiti u više
manjih komponenata.
Proces izgradnje aplikacije može ići odozdo prema gore ili odozgo prema dolje. Kod
manjih aplikacija, uglavnom je jednostavnije raditi odozgo prema dolje, ali kod većih je bolji
pristup odozdo prema dolje i odmah testirati dijelove aplikacije.
Za uvođenje interaktivnosti u aplikaciju, potrebno je mijenjati pozadinski model podataka.
React za to koristi stanje (engl. state). Stanje bi uvijek trebalo sadržavati minimalnu količinu
podataka, a sve što može, računati u trenutku kada je potrebno. Na primjer, za neku listu podataka
nije potrebno čuvati varijablu s brojem članova liste, već se članovi mogu prebrojati u trenutku
kada je taj podatak potreban.
Potrebno je odrediti koje će komponente sadržavati stanje. Većina komponenti trebala bi
biti bez stanja, kako bi se stanje postavilo na najlogičnije mjesto i kako bi se minimalizirala
redundantnost. Takve komponente preuzimaju potrebne podatke putem proslijeđenih svojstava
(props), od drugih komponenti koje ih pozivaju (tzv. roditeljske komponente – engl. parent
components). Roditeljske se komponente nalaze više u hijerarhiji i inkapsuliraju svu interakcijsku
logiku, a njihova "djeca" (engl. children components) generiraju podatke.
Komponente koje bi trebale imati stanje su one koje odgovaraju na unos korisnika, zahtjev
sa poslužitelja ili zahtijevaju prolaz određenog vremena. Stanje bi trebalo sadržavati podatke koje
bi komponenta mogla promijeniti u s ciljem promjene korisničkog sučelja; radi se o minimalnoj
količini podataka dovoljnoj za prikaz stanja korisničkog sučelja. Stanje ne bi trebalo sadržavati
duplikat podataka iz svojstava (props) (ako je moguće, svojstva bi trebala biti jedini izvor
10
podataka), druge React komponente (njih se definira unutar render() metode) i izračunate
podatke (svi bi izračuni trebali ići unutar render() ).
Funkcija render()može vratiti samo jedan čvor iz stabla elemenata. Ako komponenta
vraća više elemenata (npr. naslov i podnaslov), potrebno ih je spojiti u jedan div, span ili sličan
HTML element.
Na slici 3.2. prikazan je primjer jedne vrlo jednostavne aplikacije i komponente od kojih
se sastoji – svaka je komponenta uokvirena određenom bojom. Aplikacija sadrži tablicu proizvoda
koju je moguće pretraživati te je podijeljena na:
- TablicaProizvodaSPretraživanjem (narančasto) – objedinjuje sve druge elemente
- TrakaPretraživanja (plavo) – prima korisnički unos
- TablicaProizvoda (zeleno) - prikazuje filtrirane podatke, temeljene na korisničkom unosu
- KategorijaProizvoda (žuto) – prikazuje red tablice s naslovom kategorije
- Proizvod (crveno) – prikazuje red tablice za jedan proizvod
Slika 3.2. Prikaz React komponenti na primjeru tablice proizvoda
11
Na temelju prikazanih okvira moguće je proizvesti hijerarhiju komponenata. Okvir koji se
nalazi unutar drugog okvira trebao bi se u hijerarhiji prikazati kao njegovo dijete:
- TablicaProizvodaSPretraživanjem
- TrakaPretraživanja
- TablicaProizvoda
- KategorijaProizvoda
- Proizvod
Od izvedenih komponenti, samo TablicaProizvodaSPretraživanjem prima stanje, kao
vrhovna komponenta aplikacije. Sve ostale komponente primaju isključivo svojstva, proslijeđena
od strane TablicaProizvodaSPretraživanjem. TrakaPretraživanja prima korisnički unos i šalje
povratni poziv komponenti TablicaProizvodaSPretraživanjem u kojem je obavještava da se stanje
treba promijeniti. [5]
3.3. React DevTools
React Development Tools je alat za pomoć pri razvijanju React aplikacija, dostupan kao
proširenje u Chrome i Firefox preglednicima. Na slici 3.3. prikazan je primjer korištenja React
Development Tools.
Slika 3.3. React DevTools
12
React DevTools se prikazuje kao dodatna kartica u već postojećim DevTools korištenog
preglednika. Omogućuje pregled hijerarhije komponenata u aplikaciji, kao i proučavanje i
uređivanje njihovih stanja i svojstava.
Na slici 3.3. prikazana je NReact kartica u kojoj su ispisana svojstva (Props) i stanje (State)
odabranog elementa <Todos>. Vidljivo je da sadrži polje s 3 objekta te je moguće proučiti svaki
objekt i izmijeniti njegova svojstva.
13
4. REDUX KNJIŽNICA ZA UPRAVLJANJE STANJEM
JEDNOSTRANIČNE APLIKACIJE
Redux je JavaScript knjižnica za upravljanje stanjem aplikacije s minimalnim API-jem i
predvidivim ponašanjem. Sprema stanje aplikacije u stablo objekata unutar jedinstvenog spremišta
(engl. store). Jedini mogući način za promjenu stanja je odašiljanje akcije, objekta koji opisuje
događaj, a funkcija naziva reduktor definira točno kako akcija mijenja stablo stanja.
Umjesto direktne izmjene trenutnog stanja, reduktor uzima staro stanje i na njega
primjenjuje odaslanu akciju kako bi vratio novo stanje aplikacije. Dakle, stanje Redux aplikacije
se nikada ne mijenja, već se svaki put izrađuje kopija starog stanja koja se zatim dovodi u novo
stanje. Tijekom izrade aplikacije, kreće se s jednim „korijenskim“ reduktorom (engl. root reducer),
koji se po potrebi, kako aplikacija raste, dijeli u više manjih reduktora namijenjenih za pojedine
dijelove stabla stanja.
Iako se može koristiti sam za sebe, kao i s drugim Javascript knjižnicama, Redux radi
posebno dobro s React radnim okvirom (engl. framework), budući da on omogućuje opisivanje UI
kao funkcije stanja, a Redux kao odgovor na akcije ažurira stanje. Poseban paket react-redux je
dostupan kao poveznica između Reduxa i Reacta.
4.1. Redux elementi
4.1.1. Akcije
Akcije (engl. actions) prenose podatke iz aplikacije u spremište, i jedini su izvor
informacija za spremište. Šalju se funkcijom store.dispatch(). Obični su JavaScript objekti i
moraju imati svojstvo type koje opisuje vrstu akcije koja se izvodi, a tipično se definiraju kao
konstante vrste string (znakovni niz). Osim obaveznog svojsta type, struktura akcije je
proizvoljna, ali preporuča se da prenose minimalnu količinu podataka. Na primjer, dovoljno je
prenijeti samo identifikacijsko svojstvo (id) objekta, a ne cijeli objekt.
14
4.1.2. Kreatori akcija
Kreatori akcija (engl. action creators) su funkcije koje kreiraju akcije te u Redux aplikaciji
jednostavno vraćaju akciju. Kada je potrebno odaslati akciju, kreator akcije se proslijeđuje
dispatch() funkciji, ili se definira vezani kreator akcije (engl. bound action creator), koji
uključuje dispatch() funkciju.
4.1.3. Reduktori
Akcije prenose informaciju da se nešto dogodilo, ali ne definiraju kako događaj utječe na
stanje aplikacije. Za to su zaslužni reduktori.
Reduktor je funkcija koja prima prethodno stanje i akciju te iz njih kreira novo stanje. Bitno
je da su reduktori uvijek „čiste“ funkcije (za isti ulaz uvijek daju isti izlaz). Unutar njih se nikada
ne bi smjelo mijenjati vlastite argumente, izvoditi pozive API-ju ili preusmjeravanja (engl. routing
transitions) te pozivati „nečiste“ funkcije (npr. Date.now() ili Math.random()).
Bitno je napomenuti da se stanje aplikacije nikada ne mijenja direktno, već se kreira kopija
na koju se primjenjuje akcija kako bi se dobilo novo stanje. U slučaju da je došlo do neke pogreške
ili se funkcija iz bilo kojeg razloga ne može provesti, definira se slučaj u kojem se vraća staro,
nepromijenjeno stanje.
Kao što akcije kao argument primaju minimalnu količinu podataka, tako reduktori ne
trebaju primiti cijelo stanje. Dovoljno im je proslijediti dio stanja koje je potrebno izmijeniti, a
zatim se dijelovi ukomponiraju u globalno stanje. Funkcija combineReducers() poziva sve
reduktore, svakog sa svojim djelićem stanja, i kombinira njihove rezultate u jedinstveni objekt.
Rezultat funkcije se uglavnom sprema u varijablu koja se naziva korijenski reduktor (engl.
rootReducer). Ovakvo logičko razdvajanje reduktora naziva se kompozicija reduktora (engl.
reducer composition).
15
4.1.4. Spremište
Store je objekt koji kombinira akcije i reduktore te ih povezuje sa stanjem. Njegovi su
zadaci da:
Čuva stanje aplikacije,
Omogućuje pristup stanju pomoću getState() funkcije,
Omogućuje nadogradnju stanja pomoću dispatch(action),
Registrira listenere (funkcije koje „osluškuju“ stanje i reagiraju na promjene) pomoću
funkcije subscribe(listener),
Uklanja registraciju listenera pomoću funkcije koju vraća subscribe(listener).
Aplikacija ima jedan jedinstveno spremište. Ako je potrebno razdvojiti dio logike za
rukovanje podacima, ne radi se više spremišta, već se rade novi reduktori (kompozicija reduktora).
Spremište se kreira funkcijom createStore(), kojoj se kao argument prosljeđuje
rootReducer, a opcionalno i početno stanje aplikacije. Ovo je korisno kada je potrebno uskladiti
stanje klijenta s Redux aplikacijom na poslužitelju.
4.2. Tok podataka
Redux arhitektura zasniva se na strogo jednosmjernom toku podataka, što znači da svi
podaci unutar aplikacije imaju isti životni ciklus pa je logika aplikacije predvidiva i jednostavna
za razumijevanje. Također, pridonosi normalizaciji podataka, da bi se izbjeglo slučajno stvaranje
više međusobno nezavisnih, nepotrebnih kopija istih podataka.
Za razliku od dvosmjernog toka podataka, u kojem su elementi korisničkog sučelja (engl.
User Interface – UI) dinamički povezani s modelom podataka (engl. model data) tako da svaka
promjena korisničkog sučelja automatski mijenja model podataka i obrnuto, u jednosmjernom toku
podataka model je jedini izvor podataka. U Redux aplikacijama ovaj se model naziva spremište
(engl. store). Promjene korisničkog sučelja ne mijenjaju izravno stanje, već odašilju poruke
spremištu i samo spremište ima mogućnost mijenjanja stanja aplikacije. Na slici 4.1. prikazan je
tok podataka Redux aplikacije.
16
Slika 4.1. Tok podataka u Redux aplikaciji
Životni ciklus svake Redux aplikacije slijedi 4 koraka:
1. Poziva se funkcija store.dispatch(action) koja opisuje što se dogodilo.
2. Spremište poziva reduktor funkciju s trenutnim stanjem i prethodno odaslanom akcijom
kao argumentima te reduktor pomoću njih određuje novo stanje.
3. Korijenski reduktor po potrebi kombinira rezultate drugih reduktora kako bi kreirao
jedinstveno stablo stanja (ako je korištena funkcija combineReducers() ).
4. Spremište sprema stablo stanja koje je vratio korijenski reduktor kao trenutno stanje
aplikacije. Ako postoje funkcije "pretplaćene" na promjene stanja (listener funkcije
registrirane pomoću store.subscribe(listener) ), one se sada pozivaju, a korisničko
sučelje se ažurira da bi prikazalo najnovije stanje.
4.3. Redux DevTools
Redux DevTools je alat za pomoć pri razvoju Redux aplikacija, dostupan kao proširenje u
Chrome i Firefox preglednicima. Sprema svaku izvedenu akciju i stanje u povijest aplikacije.
Najvažnija svojstva su:
- omogućuje proučavanje stanja i akcija,
- omogućuje "povratak kroz vrijeme" poništavanjem izvedenih akcija,
- moguće je poništiti neku staru akciju bez utjecaja na one izvedene kasnije,
- u slučaju promjene koda reduktora preispituje svaku već izvedenu akciju,
17
- ako reduktor zakaže, prikazuje točnu akciju pri kojoj se to dogodilo, i grešku koja je
pritom javljena.
Na slici je prikazan primjer korištenja Redux DevTools; prikazane su dvije izvedene akcije
(ADD_TODO) i odgovarajuće promjene stanja (engl. store mutations). Opcija disable poništava
pojedinu akciju, Reset vraća aplikaciju u početno stanje, Commit pretvara trenutno stanje u početno
stanje i briše sve spremljene akcije, a Revert vraća u posljednje commitano stanje.
Slika 4.2. Redux DevTools
18
4.4. Povezivanje Reduxa s Reactom
Kako bi se Redux aplikacija povezala s React komponentama, potrebno je instalirati paket
react-redux, koristeći naredbu npm install --save react-redux .
Ovaj paket poveznica potiče razdvajanje React komponenti u prezentacijske i logičke
(engl. presentational and container components). Upravo kako im nazivi govore, prezentacijske
komponente trebale bi isključivo opisivati kako što izgleda i biti nezavisne o ostatku aplikacije, a
logičke komponente bi trebale definirati kako što funkcionira. Kod nekih je komponenti teško
razdvojiti prezentaciju od logike, pa je moguće ostaviti ih zajedno, posebno ako se radi o jako
malim komponentama. Pregled glavnih značajki komponenti dan je u tablici, a širi opis u nastavku.
Prezentacijske komponente imaju sljedeća svojstva:
Opisuju kako stvari izgledaju.
Mogu sadržavati druge prezentacijske i logičke komponente i uglavnom sadrže DOM
markup i vlastite stilove.
Dozvoljavaju preuzimanje putem this.props.children.
Ne ovise o ostatku aplikacije, poput akcija i spremišta.
Ne definiraju kako se podaci učitavaju ili izmjenjuju.
Primaju podatke i povratne pozive (engl. callbacks) isključivo preko svojstava
Uglavnom nemaju vlastito stanje (ako imaju, to je stanje korisničkog sučelja).
Logičke komponente:
Opisuju kako stvari funkcioniraju
Mogu sadržavati druge prezentacijske i logičke komponente, ali uglavnom nemaju DOM
markup ni vlastite stilove.
Dobavljaju podatke i opisuju ponašanje prezentacijskih ili drugih logičkih komponenti.
Pozivaju akcije i prosljeđuju ih prezentacijskim komponentama kao povratne pozive.
Često sadrže stanje i služe kao izvor podataka.
Uglavnom se ne pišu ručno, već se kreiraju pomoću "komponenti višeg reda", poput
connect() funkcije u Reduxu. [6]
19
Tablica 4.1. Glavne značajke prezentacijskih i logičkih komponenti [7]
Components Containers
Svrha Kako što izgleda (markup,
stilovi) Kako što funkcionira
Svjesne Reduxa Ne Da
Način čitanja podataka Iz svojstava (props) Iz Redux spremišta
(subscribe to state)
Način promjene podataka Povratni pozivi iz svojstava Odašilju akcije
Način kreiranja Pišu se ručno Uglavnom ih generira React
Redux
Logička komponenta mogla bi se opisati i kao obična React komponenta koja poziva
funkciju store.subscribe() kako bi očitala dio stabla stanja i proslijedila svojstva
prezentacijskoj komponenti koju generira. Takva bi se komponenta mogla definirati i ručno, ali
Redux nudi funkciju connect(), koja optimizira izvođenje i sprječava nepotrebna ponavljanja
generiranja aplikacije. Funkcija prima dva ili tri argumenta. Prvi je funkcija mapStateToProps()
koja iz stabla stanja dohvaća potrebne podatke koji se prosljeđuju prezentacijskoj komponenti.
Drugi je argument funkcija mapDispatchToProps(), samo ako komponenta odašilje neku akciju,
a treći je sama prezentacijska komponenta koja prikazuje podatke koji su dohvaćeni.
import { connect } from 'react-redux'
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
20
Prednosti razdvajanja komponenti:
Razdvojenost problema - pridonosi boljem razumijevanju aplikacije i korisničkog sučelja.
Iskoristivost koda - prezentacijske komponente se mogu koristi s potpuno različitim
izvorima podataka.
Skrivanje logike - mijenjajući isključivo prezentacijske komponente, moguće je urediti i
prilagoditi korisničko sučelje prema želji, bez zadiranja u aplikacijsku logiku.
Komponente koje definiraju raspored, poput navigacijskih okna, zaglavlja i podnožja,
mogu se opisati samo jednom i zatim koristiti na različitim dijelovima aplikacije, bez
nepotrebnog ponavljanja istog koda.
21
5. DODATNI ALATI ZA RAZVOJ JEDNOSTRANIČNE APLIKACIJE
5.1. npm alat za upravljanje JavaScript programskim paketima
Node.js je okruženje otvorenog koda (engl. open-source) za razvoj različitih aplikacija i
alata, primarno koristeći JavaScript programski jezik i kolekciju "modula" koji odrađuju jezgrene
funkcionalnosti. Koristi se za izgradnju mrežnih programa i alata, poput web poslužitelja.
Među brojnim funkcionalnostima, nudi i alat za upravljanje paketima, npm. Koristi se za
instalaciju Node.js programa iz npm registra, organizaciju i instalaciju te upravljanje drugim
Node.js programima. Omogućuje jednostavno upravljanje međuzavisnim klijentskim resursima te
organizaciju JavaScript modula.
Npm registar sadrži sve pakete potrebne za razvoj React i Redux aplikacija. Sve zavisnosti,
odnosno paketi instalirani tijekom razvoja aplikacije, zapisuju se u datoteku package.json.
package.json tako služi kao dokumentacija o zavisnostima projekta, omogućuje definiranje
verzije paketa o kojoj ovisi i olakšava reprodukciju istog projekta. Kreira se naredbom npm init.
Konzola zatim traži unos osnovnih podataka o projektu (ime, verzija, opis, ulazna datoteka,
skripte, autor, licenca) i kreira package.json dokument.
Definiraju se dvije grupe paketa – dependencies (paketi potrebni projektu u produkciji) i
devDependencies (paketi potrebni samo tijekom razvoja i testiranja). Paketi se mogu unositi
ručno, ali puno ih je jednostavnije i pametnije zapisivati automatski, prilikom instalacije paketa,
korištenjem zastavica --save ili --save-dev.
npm install <package_name> --save -> dodaje zavisnost u dependencies
npm install <package_name> --save-dev -> dodaje zavisnost u devDependencies
5.2. Webpack module bundler
Webpack je alat koji uzima JSX, React dokumente i sve njihove zavisnosti te ih kompajlira
u jedan JavaScript dokument (engl. bundle – paket, snop) namijenjen web pregledniku.
Omogućuje zapis svih željenih postavki u jedan dokument (webpack.config.js) iz kojeg se
zatim cijela klijentska strana aplikacije može izgraditi samo jednom naredbom. Minimum
informacija koje webpack.config.js zahtijeva su ulazne i izlazne točke. U varijabli APP_DIR
22
zapisan je put do direktorija u kojem se nalazi kod React aplikacije, a BUILD_DIR sadrži direktorij
u koji će se spremiti generirani paket. Slično, varijabla entry definira koji dokument služi kao
ulazna točka aplikacije, a output definira naziv i lokaciju paketa.
// webpack.config.js
var webpack = require('webpack');
var path = require('path');
var BUILD_DIR = path.resolve(__dirname, 'src/client/public');
var APP_DIR = path.resolve(__dirname, 'src/client/app');
var config = {
entry: APP_DIR + '/index.jsx',
output: {
path: BUILD_DIR,
filename: 'bundle.js'
}
};
module.exports = config;
5.3. Babel
Pri izgradnji React i Redux aplikacija, uvriježeno je i korisno koristiti JSX sintaksu i ES6
standard koji, međutim, nisu podržani u svim preglednicima. Stoga je potreban alat koji će prevesti
JSX i ES6 kod u klasični JavaScript, podržan u svim preglednicima, a Babel je upravo to.
Babel je paket koji učitava odgovarajuće priključke (engl. plug-ins) za prijevod ES6 i JSX
sintakse. Instaliraju se pomoću npm-a:
npm i babel-loader babel-preset-es2015 babel-preset-react -S
Zatim je potrebno kreirati datoteku .babelrc u kojoj će pisati da babel-loader treba koristiti
upravo instalirane priključke:
{
"presets" : ["es2015", "react"]
}
23
Konačno, u webpack.config.js potrebno je reći Webpacku da koristi babel-loader dok
kreira paket:
// već postojeći kod ....
var config = {
// već postojeći kod ....
module : {
loaders : [
{
test : /\.jsx?/,
include : APP_DIR,
loader : 'babel'
}
]
}
}
Za svaki loader definira se koje vrste datoteka treba obrađivati. U ovom slučaju, babel će
obraditi .js i .jsx datoteke iz direktorija APP_DIR.
24
6. DJANGO ONLINE JUDGE
Praktični zadatak za završni rad bila je izrada klijentskog dijela jednostranične aplikacije
Django Online Judge koristeći ReactJS radni okvir. Prilikom izrade, Reactu je pridružen Redux.
Redux kontrolira promjene stanja, a React generira poglede stanja (engl. views).
6.1. Postavljanje okruženja
Prvi je korak instalacija potrebnih alata. Za početak, potrebno je instalirati npm kao
upravitelja JavaScript programskim paketima i Webpack za kreiranje paketa. Od ostalih paketa,
neki su instalirani unaprijed jer se znalo/očekivalo da će biti potrebni, a neki su se dodali tijekom
razvoja aplikacije. Konačna lista, iz package.json:
"devDependencies": {
"babel-core": "^6.7.6",
"babel-loader": "^6.2.4",
"babel-plugin-transform-class-properties": "^6.3.13",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-0": "^6.3.13",
"history": "^2.1.1",
"jquery": "^2.2.3",
"react": "^15.0.1",
"react-dom": "^15.0.1",
"react-hot-loader": "^1.3.0",
"react-router": "^2.3.0",
"redux-devtools": "^3.3.1",
"redux-devtools-dock-monitor": "^1.1.1",
"redux-devtools-log-monitor": "^1.0.11",
"webpack": "^1.13.0",
"webpack-bundle-tracker": "0.0.93",
"webpack-dev-server": "^1.14.1"
},
"dependencies": {
"babel-plugin-transform-object-rest-spread": "^6.8.0",
"express": "^4.13.4",
"express-stormpath": "^3.1.2",
"humps": "^1.1.0",
25
"isomorphic-fetch": "^2.2.1",
"lodash": "^4.13.1",
"normalizr": "^2.1.0",
"react-bootstrap": "^0.28.5",
"react-redux": "^4.4.5",
"react-router-redux": "^4.0.4",
"redux": "^3.5.2",
"redux-logger": "^2.6.1",
"redux-thunk": "^2.1.0"
}
}
Korišteni uređivač teksta je Sublime, koji ima mogućnost instalacije Babel proširenja za
tumačenje JSX sintakse i odgovarajuće označavanje teksta, kako bi pisanje koda bilo
jednostavnije.
6.2. Struktura aplikacije
Aplikacija se sastoji od više komponenata. Kao što je opisano u poglavljima o Reactu i
Reduxu, definiraju se:
Prezentacijske komponente
Logičke komponente
Akcije
Reduktori
Spremište
Za jasniji pregled strukture, svaka je vrsta komponenti spremljena u svoj direktorij, a dio
prezentacijskih komponenti je izdvojen u direktorij pages. To su komponente više u hijerarhiji,
koje predstavljaju logičku podjelu aplikacije po podacima koje prikazuju – Contests, Challenges,
Problems, te Login i App kao početni ulaz u aplikaciju. Slika 5.1. prikazuje organizaciju direktorija
i komponenata aplikacije Django Online Judge.
26
Slika 5.1. Organizacija direktorija i komponenata u aplikaciji Django Online Judge
Index.js je ulazna točka aplikacije, što je definirano u datoteci webpack.config.js. Sadrži
funkciju ReactDOM.render() koja uzima dva argumenta – React element koji se generira
(Provider) i DOM element u kojem će se generirati. DOM element se u ovom slučaju nalazi po
svojstvu id="app" pa u HTML dokumentu u kojem će se generirati mora postojati odgovarajući
element. U Django Online Judge, budući da se koristi webpack koji kreira paket koji objedinjuje
cijeli kod aplikacije, potrebno je učitati taj paket (bundle skripta).
// index.js
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<Route path="/login" component={LoginApp} />
<Route path="/" component={App} onEnter={requireAuth} />
<Route path="/contest/" component={ContestApp}
onEnter={requireAuth} />
/* … */
</Router>
</Provider>,
document.getElementById('app')
)
27
<!-- index.html -->
<body>
<div id="app"></div>
<script src="/bundles/dev.min.js"></script>
</body>
Komponenta App je korijen aplikacije (engl. root component). Stoga mora biti
inkapsulirana u element Provider. Provider omogućuje svim logičkim komponentama pristup
spremištu bez da ga eksplicitno prosljeđuje, koristeći React context – svojstvo koje omogućuje
prosljeđivanje svojstava niz stablo komponenti bez stalnog prosljeđivanja svojstava.
Komponenta usmjernik (Router) iz paketa react-router uključuje povijest kretanja koja
prati promjene u adresnoj traci preglednika i sprema URL-ove kao lokacijske objekte koje
usmjernik može koristiti za određivanje ruta i generaciju pravilnog skupa komponenti. Povijest
Django Online Judge aplikacije definirana je u konstanti history kao spoj browserHistory i
spremišta.
const history = syncHistoryWithStore(browserHistory, store)
Povijest preglednika (engl. browser history) jedna je od tri najčešće korištene vrste
povijesti, uz hashHistory i createMemoryHistory, te je preporučena vrsta za korištenje uz React
Router.
Browser history koristi History API ugrađen u preglednik kako bi manipulirao URL-ovima
i kreirao prave URL-ove oblika example.com/some/path.
React-router-redux knjižnica olakšava usmjeravanje spremajući podatak o lokaciji u
spremište pomoću funkcije syncHistoryWithStore(). Time je omogućeno spremanje i
ponavljanje korisničkih akcija.
Primjer logičke komponente u Django Online Judge aplikaciji je ContestListContainer.
Funkcija mapStateToProps() kao argument prima stanje iz kojeg očitava i definira polje objekata
contests. Funkcija mapDispatchToProps() definira akciju koja će biti odaslana u slučaju nekog
događaja. Zatim se u funkciji connect() proslijeđuje te podatke prezentacijskoj komponenti
ContestList kako bi se kreirala nova logička komponenta.
28
// ContestListContainer.js
const mapStateToProps = (state) => {
const contestsArr = Object.keys(state.entities.contests).map
(key => state.entities.contests[key])
/* …još koda…*/
}
const mapDispatchToProps = (dispatch) => {
return {
onContestClick: (name) => {
dispatch(openContest(name))
},
}
}
const ContestListContainer = connect(
mapStateToProps,
mapDispatchToProps
)(ContestList)
export default ContestListContainer
Prezentacijska komponenta ContestList određuje kako će se primljeno polje objekata
generirati, odnosno prikazati na korisničkom sučelju. Kreira tablicu koja će sadržavati listu
contests, ali ne ispisuje ih direktno, već poziva drugu prezentacijsku komponentu,
ContestInList, zaduženu za prikaz pojedinog contest kao jednog retka u tablici. To je primjer
razdvajanja komponenti na način da svaka komponenta odrađuje minimalni zadatak. Svaki
contest unutar polja sadrži jedinstveno identifikacijsko svojstvo (key) pomoću kojeg se
prosljeđuje u ContestInList.
// ContestList.js
const ContestList = ({ contests, onContestClick }) => (
<table>
<tbody>
{
contests.map(contest =>
<ContestInList
key={contest.name}
{...contest}
29
onClick={() => onContestClick(contest.name)}
/>
)}
</tbody>
</table>
)
ContestInList kao svojstva prima name, start i finish. Ne zna otkud su ti podaci došli
niti ih može promijeniti, samo određuje kako će se generirati – definira jedan redak tablice u kojem
će se primljeni podaci ispisati. Osim toga, za svako primljeno svojstvo definira vrstu podatka koji
sadrži (propType). React.PropTypes služi za validaciju podataka kako bi se osiguralo da se
koriste pravilno. Ako se proslijedi nepravilna vrijednost, React javlja grešku koja se ispisuje u
konzoli.
// ContestInList.js
const ContestInList = ({ name, start, finish, onContestClick, onResultsClick })
=> {
return (
<tr>
<td><Glyphicon glyph="time" /></td>
<td onClick={onContestClick}>{name}</td>
<td>{start} - {finish}</td>
<td onClick={onResultsClick}>Results</td>
</tr>
)
}
ContestInList.propTypes = {
name: PropTypes.string.isRequired,
start: PropTypes.string.isRequired,
finish: PropTypes.string.isRequired,
onContestClick: PropTypes.func.isRequired,
onResultsClick: PropTypes.func.isRequired
}
export default ContestInList
30
6.3. Akcije i tok podataka
U teorijskom dijelu završnog rada opisane su Redux akcije i kreatori akcija te sinkroni tok
podataka – svaki put kada se akcija odašalje, stanje se odmah ažurira.
Django Online Judge aplikacija treba podržavati asinkrone akcije, budući da podatke
dohvaća s vanjskog API-ja. Međutim, Redux sam po sebi ne podržava asinkrone akcije pa je
potrebno uključiti posrednika (engl. middleware) – softver koji definira kako će aplikacija
komunicirati s vanjskim poslužiteljem. Django Online Judge koristi posrednik redux-thunk koji
upravlja dispatch() metodom spremišta na način da omogućuje odašiljanje ne samo akcija, već
i funkcija ili Promisea. Takav kreator akcije naziva se thunk. Funkcije koje thunk odašilje ne
moraju biti čiste (čista akcija svaki put za isti ulaz mora vratiti isti izlaz), što omogućuje izvođenje
asinkronih poziva API-ju. Ako se koristi više posrednika, drugi posrednik može preuzeti što je
odaslano i proslijediti odgovarajuću akciju dalje. Zadnji posrednik u nizu mora odaslati akciju kao
običan objekt i zatim Redux nastavlja sinkroni tok podataka.
Posrednik se uključuje u aplikaciju pri kreiranju spremišta, korištenjem funkcije
applyMiddleware(), kao što je prikazano u kodu:
//configureStore.prod.js
//...
export default function configureStore(preloadedState) {
return createStore(
rootReducer,
preloadedState,
applyMiddleware(thunk, api)
)
}
Uz Thunk posrednik, Django Online Judge koristi i vlastitog posrednika koji služi za
komunikaciju s API-jem. Definira 4 funkcije, za 4 različite metode HTTP zahtjeva:
callApiToGet() - dohvaća podatke, koristi GET metodu,
callApiToPost() - šalje podatke unesene od strane korisnika, koristi POST metodu,
callApiToPut() - šalje podatke unesene od strane korisnika, koristi PUT metodu,
callApiToDelete() - briše podatke, koristi DELETE metodu.
31
Funkcije vraćaju podatke u JSON formatu koje posrednik prosljeđuje Redux aplikaciji.
Pritom koristi alat normalizr koji normalizira podatke prema definiranim shemama kako bi ih
aplikacija mogla koristiti. Naime, Reduxu je jako teško iščitati podatke iz ugniježđenih odgovora
API-ja. Normalizr pomoću dobivenih JSON podataka i definiranih shema zapisuje sve Contests,
Challenges i Problems u objekt entities, pri čemu sve ugniježđene podatke zamjenjuje njihovim
identifikacijskim atributima. Iz ovakvih "plošnih" podataka jednostavno je kreirati normalizirano
stablo i ažurirati ga kad se dohvate novi podaci.
//iz api.js – shema za normalizaciju elemenata contests
const contestSchema = new Schema('contests', {
idAttribute: contest => contest.name
})
//iz api.js – funkcija callApiToGet()
function callApiToGet(endpoint, schema) {
const fullUrl = (endpoint.indexOf(API_ROOT) === -1) ? API_ROOT + endpoint :
endpoint
return fetch(fullUrl, { headers: {'Authorization': 'Token ' +
localStorage.token }})
.then(response =>
response.json().then(json => ({ json, response }))
).then(({ json, response }) => {
if (!response.ok) {
return Promise.reject(json)
}
const camelizedJson = camelizeKeys(json)
return Object.assign({},
normalize(camelizedJson, schema)//,
//{ nextPageUrl }
)
})
}
32
export default store => next => action => {
// …
switch (method){
case 'GET':
return callApiToGet(endpoint, schema).then(
response => next(actionWith({
response,
type: successType
})),
error => next(actionWith({
type: failureType,
error: error.message || 'Something bad happened'
}))
)
//…
33
6.4. Autentikacija korisnika
U produkcijskoj verziji Django Online Judge aplikacije koristit će se prijava i autentikacija
pomoću AAI@EduHr elektroničkog identiteta. Za potrebe razvoja aplikacije, implementirana je
jednostavna autentikacija pomoću tokena. Na slici 6.1. prikazan je dijagram interakcija pri
autentikaciji korisnika pomoću tokena.
Tok događaja vezanih uz autentikaciju je:
1. Ako korisnik pokuša pristupiti aplikaciji bez autenticiranja, preusmjerava se na stranicu za
prijavu (engl. login).
2. Uneseni korisnički podaci se prosljeđuju DRF-u. Ako su važeći, korisniku se dodjeljuje
autentikacijski token.
3. Token se sprema u lokalni spremnik u pregledniku kako bi pri daljnjim HTTP zahtjevima
mogao automatski biti proslijeđen u zaglavlju zahtjeva.
4. Prijavljeni korisnik se preusmjerava na aplikaciju, a token mu omogućava pristup
podacima.
5. Kada se korisnik odjavi, token se briše iz lokalnog spremnika, a aplikacija se preusmjerava
na stranicu za prijavu.
34
Slika 6.1. Dijagram interakcija pri autentikaciji korisnika
Prilikom generacije komponente, react-router opcija onEnter poziva funkciju
requireAuth() koja, ako korisnik nije prijavljen, preusmjerava na stranicu za prijavu. Usto, pamti
trenutnu lokaciju, pa nakon prijave vraća korisnika točno na stranicu kojoj je htio pristupiti.
// iz index.js
function requireAuth(nextState, replace) {
if (!auth.loggedIn()) {
replace({
pathname:'/app/login/',
state: {nextPathname: '/app/'}
})
}
}
35
Autentikacijska logika nalazi se u datoteci auth.js, a definira funkcije za prijavu (login),
odjavu (logout), provjeru je li korisnik trenutno ulogiran (loggedIn) i dobivanje tokena
(getToken).
//auth.js
module.exports = {
login: function(username, pass, cb) {
if (localStorage.token) {
if (cb) cb(true)
return
}
this.getToken(username, pass, (res) => {
if (res.authenticated) {
localStorage.token = res.token
if (cb) cb(true)
} else {
if (cb) cb(false)
}
})
},
logout: function() {
delete localStorage.token
},
loggedIn: function() {
return !!localStorage.token
},
getToken: function(username, pass, cb) {
$.ajax({
type: 'POST',
url: 'http://localhost:8000/api/obtain-auth-token/',
data: {
username: username,
password: pass
},
success: function(res){
cb({
authenticated: true,
token: res.token
36
})
}
})
},
}
Komponenta za prijavu (Login) traži unos korisničkog imena i lozinke te poziva funkciju
login() definiranu u auth.js koja, ako su korisnički podaci ispravni, od API-ja dohvaća
odgovarajući token i preusmjerava sada prijavljenog korisnika na stranicu kojoj je nastojao
pristupiti.
Nakon što se u poslužiteljskom dijelu aplikacije postave potrebni uvjeti, autentikacija je
implementirana i korisnik ima pristup podacima API-ja, samo je potrebno u svaki zahtjev API-ju
uključiti zaglavlje Authorization header koji prosljeđuje autentikacijski token.
// iz middleware/api.js
function callApiToGet(endpoint, schema) {
const fullUrl = (endpoint.indexOf(API_ROOT) === -1) ? API_ROOT +
endpoint : endpoint
return fetch(fullUrl, { headers: {'Authorization': 'Token ' +
localStorage.token }})
37
7. PREDNOSTI I NEDOSTACI PREBACIVANJA APLIKACIJSKE
LOGIKE NA KLIJENTA
Pri razvoju jednostraničnih web aplikacija, uobičajeno je prenijeti aplikacijsku logiku s
poslužiteljskog na klijentski dio aplikacije. U ovom se poglavlju navode prednosti i nedostaci
takvog pristupa.
7.1. Prednosti
Prva, očita prednost je rasterećenje poslužitelja. Poslužitelj ne treba sam kreirati web stranicu,
već prosljeđuje statičke dokumente klijentu, a zatim samo odgovara na zahtjeve slanjem JSON
podataka. Takvo rasterećenje omogućuje posluživanje više klijenata nego kod tradicionalnih
aplikacija.
Umjesto dohvaćanja i učitavanja brojnih stranica dok je korisnik na nekom web sjedištu,
jednostranične aplikacije dohvaćaju sav potreban kod pri inicijalnom učitavanju stranice te se
kreira virtualni DOM, a zatim po potrebi osvježava i učitava samo dijelove korisničkog sučelja i
DOM-a. Time je korisničko iskustvo puno ugodnije, jer nema nepotrebnog čekanja da se stranica
dohvati ili ponovnog učitavanja cijele stranice, već korisnik ima osjećaj korištenja desktop
aplikacije, bez dohvaćanja resursa s vanjskog poslužitelja.
Budući da se većina resursa (HTML, CSS, skripte) učitava samo jednom, između klijenta
i poslužitelja se razmjenjuju samo podatci (engl. data), što uvelike ubrzava rad aplikacije te i
zahtijeva manju propusnost mreže (engl. bandwith). Podatci koje razmjenjuju su u JSON formatu,
što znači da je vrlo lako provesti testiranje aplikacije - dovoljno je napisati testne JSON podatke i
poslati ih aplikaciji.
Kada se sva interakcija s korisničkim sučeljem odvija isključivo na klijentu, korištenjem
JavaScripta, HTML-a i CSS-a, poslužitelj služi isključivo kao uslužni sloj za dobavljanje
podataka. Klijent treba znati samo koje HTTP zahtjeve treba slati, bez ikakvog znanja o
implementaciji poslužiteljskog dijela. Ovakvo razdvajanje (engl. decoupling) klijentskog i
poslužiteljskog dijela aplikacije osigurava iskoristivost koda i olakšava održavanje aplikacije.
Samostalnost svakog dijela znači da je moguće zamijeniti cijeli klijentski dio bez ikakvog utjecaja
na poslužiteljski, i obrnuto. Također, otvara mogućnost proširenja aplikacije; za izradu mobilne
verzije iste aplikacije dovoljno je projektirati klijentsku mobilnu aplikaciju, a poslužiteljski je dio
38
iskoristiv ovakav kakav je. S obzirom na raznolikost uređaja koji se danas koriste, ovo je velika
prednost.
7.2. Nedostaci
Jedan od nedostataka ovakvog pristupa je nedostatak kontrole na klijentskoj strani. Naime,
brzina aplikacije ovisi o pregledniku i samom računalu koji se koristi, na što se ne može utjecati.
S druge strane, kada se generiranje odrađuje na poslužiteljskoj strani i performanse su loše,
moguće ih je poboljšati npr. povećanjem broja poslužiteljskih uređaja. Iako su mogućnosti klijenta
posebno bitne za aplikacije s logikom na klijentskoj strani, zapravo utječu na svaku aplikaciju pa
se ovaj problem može javiti i s logikom na poslužiteljskoj strani.
Općenito, kompatibilnost s različitim preglednicima je upitna pa je potrebno ili razviti
aplikaciju sa što manjim zahtjevima ili osigurati više verzija, prilagođenih pojedinom pregledniku.
Neki stariji preglednici uopće ne podržavaju aplikacije pisane JavaScriptom, a neki korisnici
odluče onemogućiti JavaScript. Međutim, problemi kompatibilnosti s preglednicima postoje i kod
aplikacija s logikom na poslužiteljskoj strani, iako u manjoj količini, a gotovo sve moderne
aplikacije koriste JavaScript pa se preglednici koji ga ne podržavaju sve manje koriste.
Kod aplikacija koje se generiraju na poslužiteljskoj strani, kada klijent dohvaća stranicu,
odmah dobije učitanu stranicu. Generiranje na klijentskoj stranici znači da preglednik prvo
dohvaća kod, a zatim ga pokreće kako bi učitao stranicu. To znači da, ovisno o kompleksnosti
aplikacije i mogućnostima preglednika, prvo učitavanje stranice traje duže nego pri generiranju na
poslužiteljskoj strani (dok se kod ne pokrene, korisniku je prikazana prazna stranica). Međutim,
suvremena računala i preglednici su sposobni odraditi dodatan posao dovoljno brzo te korisnik
uglavnom i ne primjećuje dodatno vrijeme učitavanja.
Kada se aplikacija generira na poslužitelju, sve konstantne podatke (HTML, slike, CSS...),
kao i konačnu generiranu verziju aplikacije moguće je spremiti u predmemoriju (engl. cache)
preglednika, što rasterećuje poslužitelja i ubrzava učitavanje aplikacije. Generiranje na klijentu
znači da se aplikacija mora uvijek iznova graditi.
Ako se dogodi greška na aplikaciji s logikom na poslužitelju, automatski je zapisana na
poslužitelju. Kod aplikacija s logikom na klijentu, potrebno je dodatno specificirati zapisivanje
grešaka i slanje na poslužitelj kako bi se mogle zabilježiti i obraditi. Osim što je problematično,
ovakvo bilježenje često je i nepouzdano.
Generiranje aplikacije na klijentu može predstavljati problem za web tražilice. Naime,
budući da web stranica zapravo nastaje i dobiva sav svoj sadržaj u pregledniku, pretraživač web
39
stranica će vidjeti drukčiju verziju nego stranice nego krajnji korisnik, jer pretraživači ne
prepoznaju URL-ove i stranice učitane pomoću JavaScripta. Iako se sami pretraživači
nadograđuju, i dalje je pri izgradnji aplikacije potrebno imati na umu dodatnu optimizaciju za web
tražilice, koja kod aplikacija s logikom na poslužitelju nije potrebna.
7.3. Zaključak analize
Može se zaključiti da je zadržavanje aplikacijske logike na poslužiteljskoj strani aplikacije
nužno samo u slučaju kada početno učitavanje stranice mora biti veoma brzo. Za sve druge potrebe,
prebacivanje logike na klijenta je bolji izbor jer pridonosi responzivnosti aplikacije te rasterećenju
mreže i poslužitelja.
Postoji i treća opcija, u kojoj je aplikacijska raspodijeljena između klijenta i poslužitelja
tako da poslužitelj učita početnu stranicu sa svim podacima, a sve daljnje akcije preuzima klijent.
Time je osigurana brzina i responzivnost u svakom trenutku. Ovakve se aplikacije nazivaju
univerzalnim (engl. universal) ili izomorfnim (engl. isomorphic) aplikacijama, a izrađuju se
koristeći React i Node.js, pri čemu Node.js služi kao poslužitelj.
40
ZAKLJUČAK
U ranijim danima interneta, sadržaj web stranica bio je isključivo statičan i služio kao izvor
informacija. Moderne web aplikacije su interaktivne i mogu pružiti bogato korisničko iskustvo,
slično mobilnim ili desktop aplikacijama. Sadržaj stranice se učitava i mijenja dinamički, umjesto
da se odjednom dohvaća sve.
Tradicionalno se korisnička sučelja web aplikacija grade većinom pomoću HTML
predložaka. Pri razvoju modernih aplikacija se koriste moderne metode i alati, primarno HTML5,
AJAX i JavaScript. U zadnjih nekoliko godina postao je trend graditi klijentsku stranu web
aplikacije najvećim dijelom u JavaScriptu te se razvilo nekoliko radnih okvira, poput ReactJS,
AngularJS, EmberJS, MeteorJS… Tim se radnim okvirima uglavnom grade jednostranične web
aplikacije, pri čemu se aplikacijska logika u velikom dijelu prebacuje s poslužiteljske na klijentsku
stranu aplikacije.
Držanje aplikacijske logike na klijentskoj strani znači da se pri početnom učitavanju
aplikacije preuzimaju svi potrebni resursi s poslužitelja i zatim se aplikacija generira na klijentskoj
strani, unutar web preglednika. Stoga početno učitavanje stranice traje duže nego kod aplikacija s
logikom na poslužiteljskoj strani. Međutim, sva daljnja interakcija s aplikacijom je brža, jer se
ponovno ne učitava cijela stranica, nego samo dijelovi koji se izmijene. Pritom je uloga poslužitelja
svedena na jednostavno sučelje za dohvat podataka, koje prosljeđuje u JSON formatu, što je brzo
i ne opterećuje mrežu.
Pri razvoju jednostraničnih web aplikacija koristeći ReactJS radni okvir, aplikacija se dijeli
na komponente koje predstavljaju dijelove korisničkog sučelja, njihov izgled i poslovnu logiku.
Pritom je uobičajeno koristiti JSX sintaksu, „proširenje“ JavaScripta koje uključuje XML sintaksu.
Redux je knjižnica za upravljanje stanjem aplikacije koja posebno dobro radi uz React (za razliku
od ostalih JS radnih okvira) jer React omogućuje definiranje korisničkog sučelja kao funkcije
stanja, a Redux ažurira stanje kao odgovor na akcije.
41
LITERATURA I IZVORI
[1] Wasson, M.: „ASP.NET - Single-Page Applications: Build Modern, Responsive Web Apps
with ASP.NET“, s Interneta, https://msdn.microsoft.com/en-us/magazine/dn463786.aspx, 7.
rujna 2016.
[2] Single-page applications, s Interneta, https://en.wikipedia.org/wiki/Single-page_application,
7. rujna 2016.
[3] Hunt, P.: „Why did we build React?“, s Interneta,
https://facebook.github.io/react/blog/2013/06/05/why-react.html, 8. rujna 2016.
[4] Krajka, B.: „The difference between Virtual DOM and DOM“, s Interneta,
http://reactkungfu.com/2015/10/the-difference-between-virtual-dom-and-dom/, 10. rujna 2016.
[5] Hunt, P.: „Thinking in React“, s Interneta, https://facebook.github.io/react/docs/thinking-in-
react.html, 9. rujna 2016.
[6] Abramov, D: „Presentational and Container Components“, s Interneta,
https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.rjdlmnpsr, 9.
rujna 2016.
[7] Abramov, D.: „Usage with React“, s Interneta,
http://redux.js.org/docs/basics/UsageWithReact.html, 9. rujna 2016.
42
SAŽETAK
U radu je ukratko objašnjen način izrade modernih jednostraničnih web aplikacija koristeći
ReactJS radni okvir i Redux knjižnicu za upravljanje stanjem. Jednostranične web aplikacije
nastoje pružiti korisničko iskustvo slično onome desktop aplikacija, a to postižu prebacivanjem
aplikacijske logike s poslužiteljske na klijentsku stranu aplikacije. Nakon početnog učitavanja web
stranice, sve se daljnje promjene izvršavaju samo na dijelovima aplikacije, bez ponovnog
učitavanja cijele stranice. Komunikacija s poslužiteljem svedena je na dohvaćanje podataka, koje
poslužitelj dostavlja u JSON formatu. U radu se analiziraju prednosti i nedostaci ovakvog pristupa
razvoju web aplikacije te se dolazi do zaključka da prebacivanje aplikacijske logike na klijenta
uvelike pridonosi responzivnosti stranice i korisničkom iskustvu te rasterećenju poslužitelja, a nije
preporučljivo jedino u slučaju kada početno učitavanje aplikacije mora biti jako brzo.
Kao praktični dio završnog rada izrađen je klijentski dio jednostranične aplikacije Django
Online Judge koristeći ReactJS i Redux te je aplikacija opisana u radu.
Ključne riječi: jednostranične web aplikacije, klijentski dio web aplikacije, ReactJS, Redux.
ABSTRACT
The thesis paper contains a brief introduction to development of modern single-page web
applications (SPA) using ReactJS framework and Redux state management library. Single-page
applications strive to provide customer experience similar to that found in desktop apps, which is
achieved by moving the application logic from the server to the client side of the application. After
the initial loading of the webpage, all the following changes are rendered on parts of the page,
without the need for a full reload. All the server does is provide needed dana in JSON format. The
paper analyses the advantages and disadvantages of this approach to web application development
and comes to the conclusion that keeping the application logic on the client-side greatly contributes
to the application's responsiveness and user experience as well as server unloading, and is not
recommended only for applications that need the initial loading to be very fast.
The practical part of the thesis consisted of building the frontend for a single-page
application Django Online Judge using ReactJS and Redux, which is described in the paper.
Key words: single-page application, frontend, React.js, Redux.