React Native alapozó

avagy mobilalkalmazás fejlesztés (nem csak) webfejlesztőknek

React Native alapozó

avagy mobilalkalmazás fejlesztés (nem csak) webfejlesztőknek

Forrás: https://hackernoon.com/benefits-of-react-native-for-cross-platform-app-development-0gm30aa

Időm nagy részében android fejlesztőként dolgozom és arra gondoltam keresni kéne valami eszközt amivel könnyen összedobhatok iOS appokat is ha szükséges. Az Objective-C-t próbáltam megszeretni, de nem igazán sikerült. A szögletes zárójeles szintaxisra nem igazán akart ráállni a szemem. A Swiftről sem hallottam sok jót, ráadásul nem akartam csak emiatt egy új nyelvet megtanulni (hasonló okokból esett ki végül a Dart alapú Flutter is). Valami hasonló logika mentén jutottam el végül a React Native-hoz, ahol a jó öreg JavaScriptben fejleszthet az ember mobilappokat, és hát ha a Facebooknak jó, akkor bizonyára jó lesz nekem is.

Mielőtt nekiestem volna a doksiknak, megnéztem pár mintakódot ami JavaScript volt ugyan, de valami egészen fura logikával. Android fejlesztőként ugye ahhoz voltam szokva, hogy egy XML-ből felépül a UI, ahol az egyes komponenseket az appon belül objektumokként érem el és metódusokkal módosítgathatom. Ha például egy TextView-ba ki akarok írni valamit, akkor meghívom a TextView setText metódusát. No, ilyesmit egyáltalán nem láttam a kódokban. Ez valami egészen új. Ahogy aztán kicsit jobban beleástam magam a doksikba, rájöttem, hogy ez az egész nem valami új dolog, sokkal inkább egy régi ismerős.

Ha az ember webappot akar fejleszteni mindenféle kliens oldali feldolgozás és JavaScript nélkül (mondjuk PHP-ben), akkor a dolog valahogy úgy néz ki, hogy a felhasználó felküldi az adatokat a szervernek (mondjuk egy kitöltött formot), a szerver feldolgozza azokat, majd ez alapján újragenerálja az egész oldalt és visszaküldi a böngészőnek. Tehát ha írni akarunk egy nagyon egyszerű PHP szkriptet, ami egy gomb megnyomásának hatására kiírja, hogy “Button clicked!”, akkor az úgy fog működni, hogy a gomb megnyomásának hatására az oldal frissül, a szerver pedig újragenerálja az egész oldalt, csak ezúttal úgy, hogy benne legyen a “Button clicked!” szöveg. Az ilyen fajta működés egyik nagy előnye például, hogy használhatunk template-eket, amivel sokkal kényelmesebb és átláthatóbb a működés. Nos, kb. így működik a dolog react native-ban is.

A fenti kódban jól látszik ez a fajta működés. A render metódusban látható a template amiből generálódik a UI. Nekem rögtön a PHP jutott az eszembe, ahol ugyanígy be lehet ágyazni a HTML kódot mint itt a UI-t leíró XML-t, ami más kódrészeket tartalmazhat. Erre itt a {}-ek szolgálnak. Ami elsőre feltűnik az embernek, hogy ilyen beágyazás a JavaScript-ben nincsen. A JS ilyen fajta kiterjesztése a JSX amit egy előfordító fordít szabvány JS-re. A render metódus valójában egy ReactElement objektumokból álló fát ad vissza, de ezt a JSX teljesen elrejti előlünk.

A szerver oldali feldolgozás nagy hátránya, hogy mikor újrageneráljuk a teljes oldalt, azt a böngészőnek újra fel kell dolgoznia, újra legenerálni a teljes DOM-ot, stb. Ez elég nagy árnak tűnik egy vacak szöveg megváltoztatásáért. Szerencsére a React ezt okosan csinálja. Minden renderelést követően az objektum fát összehasonlítja az aktuális állapottal és mindig csak a változásokat érvényesíti, így a folyamat végén mégiscsak egy setText hívás fog történni. Ezzel a megoldással tehát megmarad a template-ek kényelme, miközben a végrehajtás ugyanolyan hatékony mint a natív megoldás esetén.

A másik dolog ami feltűnhet, az a clickHandler eseménykezelőben a setState metódus hívása. A this.state változót a konstruktorban közvetlenül állítjuk be és a render metódusban is közvetlenül olvassuk ki, az eseménykezelőben viszont a setState-n keresztül módosítjuk. Ez elsőre megint valami új dolognak tűnt, amiről később kiderült, hogy ugyancsak egy régi ismerős.

Még nagyon nagyon régen, mikor 3D engine-t írtunk (Pascalban!), úgy nézett ki a programlogika, hogy beolvastuk az inputot, legeneráltuk a tér aktuális állapotát, majd kirendereltük és megjelenítettük az objektumokat. Tulajdonképpen ez pörgött egy végtelen ciklusban. Csakhogy néha túl sokáig tartott a számolás vagy a renderelés, amitől a játék belassult. Vegyünk például egy kockát, aminek 10mp alatt kellene körbefordulnia. Írunk rá egy timert, ami ~0,028mp-enként forgat rajta egy fokot. De mi van akkor, ha a tér renderelése 0,1mp-ig tart? Ha minden forgatás után renderelünk, akkor kb. negyedére fog csökkenni a kocka forgási sebessége, így 10mp helyett 40mp-ig tart amíg körbeforog, ami nem jó. A másik lehetőség, hogy egyszerűen kidobunk 3 frame-et és csak minden negyediket rendereljük ki. Ekkor kicsit szaggatottabb lesz a mozgás (csökken az FPS — Frame Per Second), de a kocka ugyanúgy 10mp alatt fordul körbe. Ha így járunk el, nem fog használhatatlan mértékben lassulni a játék, csak csökken a folyamatosság (az FPS). Ugyanígy működik a react is. Mikor egy eseménykezelőben megváltoztatjuk az állapotot (mondjuk kiírunk egy szöveget), akkor nem biztos, hogy a UI-on történik is valami. Elképzelhető, hogy a rendszer nem ér rá, mert épp egy előző fázist renderel. Ilyenkor a változtatási kérelmek gyűlnek egy csőben, majd ha újra felszabadul a renderer, összefűzi őket és kirendereli a következő állapotot. Mivel a renderelés teljesen aszinkron, ezért ha közvetlenül piszkálnánk a state változót, megtörténhetne pl. olyan disznóság, hogy csak félig állítottuk be mikor renderel a rendszer. Ezért kell minden állapotváltoztatást a setState-ben csinálni.

Ha ezt a pár dolgot sikerül megszokni és tudunk a react logikájának megfelelően gondolkodni, akkor egy nagyon kényelmes rendszert kapunk, amivel gyorsan fejleszthetünk cross-platform alkalmazásokat JavaScriptben. A React Native segítségével egy biztos JavaScript tudással rendelkező webfejlesztőből pár hét alatt mobil app fejlesztő válhat.

Az írás további részében össze fogunk rakni egy egyszerű todo appot, ahol lehet felvenni feladatokat, illetve ki lehet törölni azokat. Első lépésben telepítsük az expo-t (a telepítéshez Node.js szükséges):

npm install -g expo-cli

Az Expo egy nagyon okos kis tool React Native-hoz, amivel nagyon könnyen fejleszthetünk illetve forgathatunk natív appokat.

expo init todo

A fenti paranccsal létrehozzuk a todo projektet (a felajánlott listából válasszuk ki a teljesen üres blank projektet). A létrehozott könyvtárat nyissuk meg a kedvenc JavaScript editorunkkal (nálam ez a VSCode), majd a könyvtárba lépve adjuk ki a a következő parancsot:

expo start

Ha elindult a gépünkön a lokális szerver, egy QR kód fog megjelenni, amit az Expo Android vagy iOS appjával bescannelve a mobilunkon elindul az alkalmazás. Ha a szerkesztőben bármit módosítunk, a mobilon automatikusan frissül a felület, így igazán gyorsan és kényelmesen megy a fejlesztés.

Ha a fenti kódot bemásoljuk az App.js fájlba, rögtön láthatjuk is a TODO appot működés közben.

A kód értelmezését kezdjük talán a végével. A styles konstansban definiáljuk az egyes komponensekhez tartozó stílusokat. A stílusok megadása a CSS-hez nagyon hasonló, még a property elnevezések is gyakran ugyanazok. A komponens stílusokról és a flexbox layout rendszerről a React Native dokumentációban olvashatunk bővebben.

A UI a szokásos módon az App osztály render metódusában van definiálva. Egy FlatList és egy TextInput komponensből áll. A FlatList a data propertyben várja a megjelenítendő adatokat egyszerű JS tömb formájában. A renderItem paraméter egy komponensre mutat, ami az egyes sorokat meg fogja jeleníteni. Ebben az esetben ez a komponens a listItem. Feltűnhet, hogy a listItem az App-tól eltérően nem egy osztály ami a Componentből származik, hanem egy egyszerű JS függvény, React Native-ban ugyanis ez a két módja van egy komponens megadásának. Vagy definiálunk egy osztályt és implementáljuk a render függvényt, vagy egyszerűen definiálunk egy függvényt, ami paraméterként megkapja a komponens property-ket, a visszatérési értéke pedig a generált UI. Az így definiált komponenseket felhasználhatjuk az UI leíró XML-ben, vagy paraméterként, mint a fenti esetben. A listItem paraméterként kapja meg a megjelenítendő elemet (item), és annak indexét (index).

A TextInput egy egyszerű beviteli mező. Az onChangeText paramétereként megadott changeTextHandler arra szolgál, hogy meg is jelenjen a szöveg amit beírunk. Itt ugyanis minden billentyűleütést követően visszaírjuk a bevitt szöveget a value paraméterbe. Az elem hozzáadását az onSubmit eseménykezelőben megadott addTask függvény végzi, ami egy új elemet fűz a state-ben lévő tasks tömb végére.

Végül a deleteTask, amit a listItem X gombja hív az átadott index alapján törli az adott elemet a tasks tömbből. Ezzel kb. le is fedtük a teljes működési logikát. Ami kimaradt, az a componentDidMount függvény, ami akkor fut le, amikor a komponens kikerül a UI-ra, így itt végezhető el minden szükséges inicializálás. Jelen esetben két eseménykezelőt definiálunk, amik a billentyűzet megjelenésekor feljebb, illetve eltűnésekor lejjebb állítják a beviteli mező helyzetét, hogy nyitott billentyűzet esetén is látszódjon amit írunk.

Most hogy minden fontos részt átbeszéltünk, jöhet a fordítás:

expo build:android

A fenti parancs az Expo build szerverét használva elkészíti az Android APK-t (vagy iOS esetén az IPA-t). A dolog előnye, hogy nem kell telepítenünk semmilyen fejlesztői környezetet, hiszen az Expo mindent intéz helyettünk. A hátránya, hogy az ingyenes változatnál sokszor sokat kell várni a build szerverre amíg kiköpi az appot. A priority buildért havi 30$-t kérnek.

Ha inkább lokálisan fordítanánk, akkor hozzunk létre egy üres React Native appot és másoljuk át a projekt tartalmát. Innen már fordíthatunk helyben Androidra és iOS-re. A dolog kicsit macerásabb mint az Expo használata esetén, hiszen telepítenünk kell a fejlesztőkörnyezeteket és iOS esetén még egy Mac-re is szükségünk lesz, cserébe ingyen van és jobban testre szabható az alkalmazás.

A fentiekből jól látható, hogy React Native segítségével pár sorból egész ügyes dolgokat össze lehet rakni, ráadásul a kód is szép és átlátható, az Expo-nak köszönhetően pedig a kényelmes és egyszerű a fejlesztés. Ha valaki webfejlesztőként szeretne valódi, natív komponenseket használó mobilappot készíteni (tehát nem csak egy beágyazott weblapot, amilyet a Cordova alapú Ionic generál), annak a React Native ideális megoldás, hiszen JavaScriptben kódolhat, a UI-t pedig HTML/CSS használatához hasonló módon építheti fel.

Nyilván sokat lehetne még írni a React Native-ról, de úgy gondolom, hogy alapozásnak ennyi elegendő. A projekt népes közösséggel rendelkezik, sok nagy cég használja mobilappok fejlesztésére, élükön természetesen a Fecebokkal. Az Interneten rengeteg leírás, tutorial és példaprogram található, így többnyire hamar választ kaphatunk a felmerülő kérdésekre.