Stav komponentu a asset-y
stav a vlastnosti komponentu, obrázky, zvuky, komponent ako trieda, inštalácia modulov tretích strán, štruktúra projektu
Záznam z prednášky
Project Torch Overview
- (slide) Naposledy sme začali s vývojom aplikácie, ktorá z nášho telefónu spraví baterku. Dnes budeme pokračovať vo vývoji, vytvoríme používateľské rozhranie a aplikácii pridáme stav.
O komponentoch
(slide) Vráťme sa ešte späť ku komponentom. Komponenty sú základným stavebným prvkom rámca React Native a sú implementované ako JavaScript-ové funkcie, ktoré vracajú React Native elementy definujúce vzhľad. Každý komponent je definovaný svojim stavom a je možné mu pri volaní nastaviť jeho vlastnosti.
(slide) Pre názornosť si môžeme základné vlastnosti komponentu predstaviť na základnom komponente
Button.<Button onPress={onPressButton} title="Turn On" color="#841584" />
Vlastnosti komponentov
- Vlastnosti komponentu (označované ako properties alebo skrátene ako props) sú do komponentu vložené pri jeho vytváraní a sú ďalej nemenné. Niektoré vlastnosti sú povinné (v prípade tlačidla sú to vlastnosti
onPressatitle). Z pohľadu zápisu sa jedná o atribúty XML elementov (v tomto prípade JSX). Zoznam vlastnsotí každého komponentu nájdete v jeho dokumentácii. V prípade komponentuButtonho nájdete tu.
Stav komponentu
Stav komponentu je naopak meniteľný a vďaka stavu sa dá na komponent pozerať ako na stavový stroj. V prípade tlačidla vieme hovoriť o dvoch stavoch:
- tlačidlo je stlačené, a
- tlačidlo nie je stlačené.
V prípade vytváranej aplikácie chceme, aby sa stlačením tlačidla baterka
- rozsvietila, ak je zhasnutá, alebo
- zhasla, ak je rozsvietená.
O našej aplikácii teda vieme povedať, že sa bude nachádzať v jednom dvoch stavov:
- zhasnutá, a
- rozsvietená.
Každý React Native komponent môze mať definovaný svoj vlastný stav, ktorý si bude následne pamätať. Stav sa môže v čase meniť napr. na základe interakcie používateľa (stlačením tlačidla na baterke).
Definovanie stavu komponentu
(slide) Pridať stav do komponentu je možné pomocou volania hook-u
useState()nasledovne:import {useState} from 'react'; const [<getter>, <setter>] = useState(<initialValue>);kde:
getterreprezentuje premennú, ktorá bude stav komponentu reprezentovať,setterreprezentuje názov funkcie, pomocou ktorej bude možné zmeniť stav, ainitialValueje hodnota, ktorá reprezentuje počiatočný stav komponentu.
Refactoring
V našom prípade môžeme nazvať stavovú premennú
isOn, ktorá bude inicializovaná na hodnotufalse(nesvieti). Funkcia na zmenu stavu sa bude volaťsetIsOn(). HookuseState()bude teda vyzerať nasledovne:const [isOn, setIsOn] = useState(false);Kód komponentu aplikácie bude následne vyzerať nasledovne:
export default function App() { const [isOn, setIsOn] = useState(false); return ( <View style={styles.container}> <Text>Hello, world!</Text> <Button onPress={function () { console.log("Button was pressed"); }} title="Turn On" /> </View> ); }Stav sa bude meniť pri stlačení tlačidla. Upravíme teda anonymnú funkciu, ktorá sa zavolá po stlačení, aby zmenila aktuálny stav komponentu:
return ( <View style={styles.container}> <Text>Hello, world!</Text> <Button onPress={function () { setIsOn(!isOn); console.log(isOn); }} title="Turn On" /> </View> );Zmenu stavu môžeme teraz vidieť vypísanú v konzole, kde sa bude stlačením tlačidla striedať hodnota
falseatrue.(slide) Na zmenu stavu môžeme zareagovať aj priamo v aplikácií zmenou textu v komponente
Texta zmenou názvu tlačidla v komponenteButton. Ak chceme vypísať obsah ľubovoľnej premennej alebo obecne výraz pomocou JSX, uzavrieme ho do kučeravých zátvoriek. Týmto spôsobom môžeme nechať vypísať ľubovoľný platný JavaScript-ový výraz. V našom prípade môžeme teda upravíme obsah komponentuTexta vlastnosťtitlekomponentuButton:export default function App() { const [isOn, setIsOn] = useState(false); return ( <View style={styles.container}> <Text>{isOn === true ? "On" : "Off"}</Text> <Button onPress={function () { setIsOn(!isOn); console.log(isOn); }} title={ isOn === true ? "Turn Off" : "Turn On" } /> </View> ); }Poznámka
Ak budete pozorne sledovať, všimnete si, že pri ošetrení stlačenia tlačidla nedôjde k aktualizovaniu hodnoty stavu okamžite. To je spôsobené tým, že funkcia
setIsOn()pridá zmenu stavu ako úlohu do fronty, ktorá sa vyprázdňuje postupne. Ak chcete ale zareagovať okamžite, funkciasetIsOn()môže ako parameter dostať funkciu, ktorá vráti nový stav. Aktualizovaný komponentButtonmôže vyzerať nasledovne:<Button onPress={function () { console.log(`>> before: ${isOn}`); setIsOn(function (state) { state = !state; console.log(`>> after: ${state}`); return state; }); }} title={isOn === true ? "Turn Off" : "Turn On"} />
Komponent pre obrázok
(slide) Jediné, čo nám aktuálne chýba do používateľského rozhrania baterky, je obrázok reprezentujúci stav baterky. Ten vytvoríme pomocou komponentu
Image, ktorý importujeme zreact-native.Komponent
Imageje možné použiť na zobrazenie obrázkov z rôznych zdrojov, ako- obrázky umiestnené v internete/sieti,
- statické obrázky,
- dočasné lokálne obrázky, a
- obrázky z lokálneho disku.
Pre rozličné typy obrázkov je možné použiť rozličné atribúty. Preto komponent
Imagenemá povinné vlastnosti, ako tomu bolo v prípade komponentuButton.Obrázky, ktoré budú reprezentovať stav aplikácie, budú jej statickou súčasťou. V projekte preto vytvoríme samostatný priečinok
assets/, kde tieto obrázky uložíme. Obrázky, ktoré použijeme, sú zo sady Tango Icons, ktoré sú vydané pod licenciou Creative Commons.

Ak ich budeme chcieť použiť v komponente
Image, použijeme vlastnosťsource, kde sa na príslušný obrázok odkážeme pomocou volaniarequire(). Upravíme teda pohľad komponentuApppridaním komponentuImagemedzi text a tlačidlo s obrázkom zhasnutej žiarovky:<Image source={require("./assets/bulb_off.png")}></Image>Tu je však potrebné si uvedomiť niekoľko vecí:
- výsledkom volania funkcie
require()je len číslo zdroja, ktoré bundler zabalil do projektu - funkcia
require()sa volá pri balení aplikácie a teda nemôže obsahovať dynamicky konštruované reťazce
- výsledkom volania funkcie
Obrázok vyberieme na základe stavu komponentu a ternárneho operátora a výsledok uložíme do premennej
image. Jeho hodnotu následne použijeme ako hodnotu vlastnsotisourcekomponentuImage:export default function App() { const [isOn, setIsOn] = useState(false); let image = isOn ? require("./assets/bulb_on.png") : require("./assets/bulb_off.png"); return ( <View style={styles.container}> <Text>{isOn === true ? "On" : "Off"}</Text> <Image source={image}></Image> <Button onPress={function () { setIsOn(function (state) { state = !state; return state; }); }} title={ isOn === true ? "Turn Off" : "Turn On" } /> </View> ); }
Stlačiteľný obrázok
Jediné, čo nám aktuálne chýba v našej aplikácii, je možnosť zmeniť jej stav stlačením obrázku. Ak sa však pozrieme na vlastnosti komponentu
Image, nenájdeme v zozname nič, čo by pripomínalo možnosť ošetriť stlačenie podobne, ako tomu bolo v prípade obrázka. V tomto prípade treba postupovať ináč.(slide) Na použitie obrázku ako tlačidla použijeme komponent
Pressable, ktorý je pred použitím treba importovať zreact-native. Ten funguje ako istý kontajner, do ktorého zabalíme komponent, ktorý má byť stlačiteľným.Poznámka
Komponent
Pressableje stále novinkou v rámci React Native a patrí medzi core komponenty. Pred jeho zavedením sa používali komponenty s prefixomTouchable*, ako napr.TouchableHighlight,TouchableOpacityaTouchableWithoutFeedback. KomponentPressableich má postupne nahradiť, takže sa dá očakávať, že uvedené komponenty postupne zmiznú z budúcich verzií rámca React Native a neodporúča sa ich používať.Do komponentu
Pressablezabalíme komponentImage:<Pressable onPress={function () { console.log("Image was pressed."); }} > <Image source={image} /> </Pressable>Keďže po stlačení obrázka očakávame rovnaké správanie ako po stlačení tlačidla, vytiahneme funkciu z komponentu
Buttonvon, nazveme jutoggleState()a použijeme ju ako callback v oboch komponentoch vo vlastnostionPress.Upravený komponentAppbude vyzerať takto:export default function App() { const [isOn, setIsOn] = useState(false); var image = isOn ? require("./assets/bulb_on.png") : require("./assets/bulb_off.png"); const toggleState = function () { setIsOn(function (state) { state = !state; return state; }); }; return ( <View style={styles.container}> <Text>{isOn === true ? "On" : "Off"}</Text> <Pressable onPress={toggleState}> <Image source={image} /> </Pressable> <Button onPress={toggleState} title={ isOn === true ? "Turn Off" : "Turn On" } /> </View> ); }
Kompletné riešenie
Po drobnom refaktorovaní, kedy vyčistíme funkciu
toggleState()a odstránime komponentText, bude výsledné riešenie vyzerať nasledovne:import React, { useState } from "react"; import { StyleSheet, View, Button, Image, Pressable, } from "react-native"; export default function App() { const [isOn, setIsOn] = useState(false); let image = isOn ? require("./assets/bulb_on.png") : require("./assets/bulb_off.png"); const toggleState = function () { setIsOn(!isOn); }; return ( <View style={styles.container}> <Pressable onPress={toggleState}> <Image source={image} /> </Pressable> <Button onPress={toggleState} title={isOn === true ? "Turn Off" : "Turn On"} /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#fff", alignItems: "center", justifyContent: "center", }, });
Komponent ako trieda
(slide) Pred uvedením hook-ov vo verzii React 16.8, sa komponenty vytvárali ako triedy. Triedy boli potomkom obecného komponentu
Componenta mali metódy, ktoré definovali ich správanie. V tejto odbočke si ukážeme, ako by komponent aplikácie vyzeral, ak by sme ho implementovali ako triedu.Výpis výsledného kódu sa nachádza nižšie.
import React, { Component } from "react"; import { StyleSheet, View, Button, Image, Pressable, } from "react-native"; export default class App extends Component { constructor(props) { super(props); this.state = { isOn: false, }; } toggleState() { const { isOn } = this.state; console.log(">> state change..."); this.setState({ isOn: !isOn }); } render() { const image = this.state.isOn ? require("./assets/bulb_on.png") : require("./assets/bulb_off.png"); return ( <View style={styles.container}> <Pressable onPress={this.toggleState.bind(this)} > <Image source={image} /> </Pressable> <Button onPress={this.toggleState.bind(this)} title={ this.state.isOn === true ? "Turn Off" : "Turn On" } /> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', alignItems: 'center', justifyContent: 'center', }, });
Prehratie zvuku pri kliknutí
- (slide) Pre dosiahnutie väčšieho efektu zabezpečíme, aby sa pri stlačení tlačidla prehral zvuk zapnutia/vypnutia baterky. Ukážeme si teda, ako je možné v aplikácii prehrávať zvuky.
Inštalácia modulov tretích strán
(slide) Rámec React Native nemá priamu podporu pre prehrávanie zvukov. Ak teda chceme v našej aplikácii prehrávať zvuky, musím podporu pre ich prehrávanie doinštalovať.
Ako odrazový mostík môžeme použiť projekt Awesome React Native. Jedná sa o web, ktorý ponúka prehľad najpopulárnejších modulov pre rámec React Native rozdelený do niekoľkých kategórií.
Poznámka
Podobných projektov je samozrejme viac. Pod názvom Awesome nájdete aj iné zoznamy a to nie len pre rámec React Native. Veľmi často ich nájdete priamo na Github-e. Otázkou je vždy ich aktuálnosť.
Samozrejme je stále možné použiť aj obecný vyhľadávač www.npmjs.com pre vyhľadávanie modulov pre node. Pri vyhľadávaní preto netreba zabudnúť pripísať aj “react native”.
(slide) Ak sa teda pozrieme do zoznamu Awesome do kategórie Media, nájdeme na prvých priečkach modul
react-native-sound, ktorý použijeme. Do projektu ho nainštalujeme príkazom:$ yarn add react-native-soundUpozornenie
Odporúča sa tiež vyčistiť cache pre zostavenie Android aplikácie, čím sa dá predísť problémom pri zostavovaní. To zabezpečíme príkazom:
$ cd android $ ./gradlew cleanBuildCache
Pridanie zvuku do projektu
Zvuk môžeme do projektu pridať dvoma spôsobmi:
Súbor zvuku nahráme priamo do priečinku zdrojov pre platormu Android. Konkrétne sa jedná o priečinok
android/app/src/main/res/raw/. Je dôležité, aby sa súbor skladal len z malých písmen a znaku podčiarkovník “_”. V tomto prípade je možné sa na súbor odkazovať priamo menom bez uvádzania cesty k nemu.Súbor uložíme do priečinku projektu a v kóde sa na neho budeme odkazovať buď pomocou príkazu
importalebo funkcierequire()podobne, ako sme to urobili v prípade obrázkov.
Podobne, ako v prípade obrázkov, použijeme na prácu so zvukovými súbormi funkciu
require().
Vytvorenie objektu Sound
Najprv v projekte naimportujeme modul
react-native-sound:import Sound from 'react-native-sound';Najprv vytvoríme objekt zvuku vytvorením objektu
Soundtýmto zápisom:const ding = new Sound(require('./assets/flashlight.wav'));Tým, že sme použili funkciu
require()na načítanie zdroja, nemusíme pri vytváraní používať žiadne ďalšie parametre, ako napr. callback pre ošetrenie prípadu, kedy k načítaniu nedôjde z dôvodu neexistencie zdroja. V prípade, že zdroj neexistuje, dôjde k chybe už pri zostavovaní balíku a nie až pri spustení aplikácie.
Prehratie zvuku
Vytvorený objekt zvuku môžeme prehrať metódou
.play(). Zvuk budeme prehrávať pri stlačení tlačidla, takže aktualizujeme funkciutoggle(). Najprv však prehráme zvuk a až potom zmeníme stav aplikácie. Tým čiastočne potlačíme prípadné spozdenie prehrávania zvuku.const toggleState = function () { ding.play(); setIsOn(!isOn); };Modul
react-native-soundmá samozrejme mnoho ďalších možností, ako napr. nastavenie hlasitosti prehrávaného zvuku, prehrávanie zvuku na ľavej/pravej strane pri stereu, opakované prehrávanie a ďalšie. Všetky možnosti nájdete na stránke projektu.
Project Structure
(slide) React a React Native sú rámce, ktoré nám nijakým spôsobom nepredpisujú štruktúru projektu alebo aplikácie. Aktuálne je náš projekt jednoduchý, ale s novými funkciami, obrazovkami, komponentmi, prekladmi sa počet súborov, z ktorých sa skladá, rapídne zväčší. Ako teda organizovať súbory tak, aby sme sa dokázali v projekte jednoducho orientovať aj napriek veľkému počtu súborov?
Existuje niekoľko spôsobov, ako organizovať štruktúru projektu. Spoločné majú hlavne logické zoskupovanie súborov podľa ich typu alebo funkcionality (tzv. type vs feature). Ukážka jedného z týchto prístupov sa nachádza napr. v projekte React Native Easy Starter.
Nebudeme rozoberať a upravovať štruktúru nášho projektu pre veci, ktoré nemáme, takže urobíme niečo, čo majú všetky štruktúry spoločné: súbory, ktoré tvoria náš projekt a nie sú automaticky vygenerované nástrojmi rámca, umiestnime do osobitného priečinku s názvom
src/.Poznámka
V Expo projekte sa dá stretnúť s názvom tohto priečinku
app/.Vytvoríme teda priečinok
src/a presunieme do neho všetko, čo sme vytvorili včetne priečinkuassets/:src/ ├── App.js └── assets/ ├── bulb_off.png ├── bulb_on.png └── flashlight.wavProstredie Visual Studio Code urobí potrebné úpravy v súboroch projektu za nás, takže projekt by mal fungovať bez prípadných zásahov. Môžeme však z priečinku
src/vytvoriť rovno modul pridaním súboruindex.js, v ktorom exportujeme komponentAppzo súboruApp.js:import App from './App'; export default App;Pre istotu môžeme overiť ešte súbor
index.jsv koreňovom priečinku projektu a v prípade potreby upraviť import:import App from './src';A okrem toho aj odkazy na ikony v súbore
app.json.Upozornenie
Pravdepodobne bude potrebné zmazať aj výsledné zostavenie Android projektu v priečinku
android/:$ cd android $ ./gradlew clean $ cd ..Okrem uvedeného môžeme aktualizovať aj súbor
.gitignore. Môžeme použiť verziu priamo od Facebook-u, ktorá sa dá rovno stiahnuť z GitHub-u projektu.
Záver
- Nabudúce sa konečne pokúsime rozsvietiť blesk na fotoaparáte.