Witam w kolejnej części serii StormSnapshot. W poprzednim wpisie mobilne aplikacje hybrydowe zebraliśmy podstawowe informacje o dostępnych technologiach oraz wybraliśmy dwa najlepiej rokujące frameworki: Ionic, oraz React native.
Dzisiaj przyjrzymy się bliżej technologii React Native i przygotujemy prostą aplikację testową.
Spis treści
React i React Native
React (nazywany również React.js lub ReactJS) to biblioteka JavaScript, rozwijana na licencji open source, do budowania interfejsu użytkownika. Projekt wspierany jest głównie przez ludzi z Facebooka i Instagrama.
Dzięki React Native można budować mobilne aplikacje, korzystając tylko z JavaScriptu. Powstała aplikacja nie jest typową aplikacją hybrydową wykorzystującą webview do prezentacji strony mobilnej, jak robi to np. PhoneGap. Zamiast tego na podstawie komponentów napisanych w składni React budowane są natywne komponenty w Javie na Androida, czy w Objective-C na iOS.
Większości frameworków hybrydowych przyświeca motto: „Write once, run anywhere” (napisz raz, uruchom wszędzie), jednak w przypadku Reacta zostało ono sparafrazowane na: „Learn once, write anywhere” (naucz się raz, pisz wszędzie).
„Write once, run anywhere”
Oddaje to dobrze ideę, która przyświeca temu projektowi. Zamiast pisać jeden kod, który będziemy mogli bez modyfikacji uruchomić na wszystkich dostępnych platformach, dostajemy jedno narzędzie, które możemy wykorzystać niezależnie czy piszemy stronę www, czy aplikację mobilną. Takie podejście teoretycznie wymaga trochę więcej wysiłku, ale pozwala uzyskać bardziej dopasowane aplikacje.
„Learn once, write anywhere”
W efekcie tego, migrując np. projekt z Androida na iOS, trzeba będzie pochylić się jedynie nad częścią komponentów, ale zdecydowana większość kodu może zostać wykorzystana jako część wspólna. Resztę kodu stanowią komponenty i funkcjonalności dostępne tylko na danym systemie operacyjnym.
Wprowadzenie do JSX
Komponenty dostępne w React pisane są zazwyczaj przy pomocy składni JSX. JSX to rozszerzenie składni JavaScript o możliwość prostego wykorzystywania tagów HTML. Użyte tagi są przetwarzane i zamieniane na odpowiednie wywołania biblioteki js.
const element = <h1>Hello, Java!</h1>;
Powyższy fragment kodu to prosta konstrukcja JSX. Składnia trochę przypomina sam widok HTML, przy jego pomocy można jednak w pełni korzystać z możliwości JavaScript.
Osadzenie wyrażeń w JSX
W JSX można wykorzystać dowolne wyrażenia JavaScript, osadzając je w nawiasach klamrowych:
function format(obj){ return 'Hello '+obj.title.toUpperCase(); } return (<Text>{format({title: 'jsx'})}</Text>);
IDE
Początkowo projekt chciałem realizować z wykorzystaniem Netbeans IDE. Niestety w obecnej wersji (8.2) Netbeans ma jeszcze bardzo słabe wsparcie dla składni JSX, nie znalazłem też żadnego pluginu spełniającego moje oczekiwania. Nie wyobrażam sobie pisania większego projektu w dowolnej technologii bez przynajmniej podstawowego wsparcia w podpowiadaniu składni.
Między innymi dlatego zdecydowałem się skorzystać z IntelliJ IDEA. Niestety IntelliJ ma wsparcie dla JavaScript i składni JSX tylko w pełnej płatnej wersji. Można jednak pobrać 30-dniową wersję próbną.
Pierwszy projekt
Przed rozpoczęciem pracy musimy przygotować środowisko programistyczne oraz wszystkie niezbędne narzędzia. Pierwsza wersja aplikacji będzie dostępna na Androida, natomiast system operacyjny, z którego korzystam to Linux Ubuntu.
Niezbędne narzędzia
Ponieważ aplikacja będzie dostępna na Androidzie, musimy zacząć od instalacji Android Studio.
React Native wykorzystuje Node.js jako silnik do budowania kodu JavaScript, dlatego potrzebujemy również jego.
sudo apt-get update sudo apt-get install nodejs sudo apt-get install npm
Kolejnym narzędziem, którego potrzebujemy, jest: React Native Command Line Interface (CLI):
sudo npm install -g react-native-cli
Więcej na temat niezbędnych narzędzi można przeczytać na React Native Getting Started.
Struktura projektu
Po instalacji wszystkich niezbędnych narzędzi możemy przejść do wygenerowania struktury przykładowego projektu. Przechodzimy do docelowego katalogu i poniższym poleceniem generujemy strukturę naszej aplikacji:
react-native init ReactNativeSampleProject
Wygenerowane pliki zawierają wszystko, co potrzeba do uruchomienia pierwszej aplikacji w React Native.
W katalogu głównym projektu znajduje się plik: index.android.js, który jest punktem wejścia dla aplikacji. W tym samym katalogu znajdziemy jeszcze katalog: node_modules zawierający pliki biblioteki React Native oraz katalog android z kodem specyficznym dla tej platformy.
Uruchomienie aplikacji na symulatorze i urządzeniu
Zaczynamy od uruchomienia serwera React Native, który będzie przechowywał pliki JavaScript. Przechodzimy do katalogu z wygenerowanym wcześniej projektem i odpalamy poniższą komendę:
sudo react-native start
W osobnej konsoli możemy już zbudować i uruchomić przygotowany projekt:
react-native run-android
Jeżeli wszystko poszło dobrze, program powinien zainstalować na dostępnym symulatorze oraz fizycznym urządzeniu nową aplikację.
Aplikacja
Nasza aplikacja ma jedno proste zadanie, mianowicie przetestowanie podstawowych możliwości React Native. W tym celu przygotujemy kilka widoków:
- ekran powitalny
Na tym widoku wyświetlimy logo oraz damy możliwość przejścia do pozostałych widoków. - lista
Wyświetlenie przewijanej listy 100 elementów. - rss
Pobranie z Internetu oraz wyświetlenie listy najnowszych postów z bloga. Poszczególne wpisy będą miały tytuł i obrazek.
Całą aplikację podzielimy na poszczególne widoki reprezentujące osobne funkcjonalności. Natomiast główny plik index.android.js będzie zawierał komponent Navigator odpowiedzialny za przejścia w aplikacji.
import React, {Component} from 'react'; import {AppRegistry, Navigator} from 'react-native'; import StartScreen from './screens/StartScreen'; import ListScreen from './screens/ListScreen'; import RssScreen from './screens/RssScreen'; import SettingsScreen from './screens/SettingsScreen'; class StartApp extends Component { renderScene(route, navigator) { if (route.name == 'StartScreen') { return <StartScreen navigator={navigator}/> } if (route.name == 'SettingsScreen') { return <SettingsScreen navigator={navigator}/> } if (route.name == 'ListScreen') { return <ListScreen navigator={navigator}/> } if (route.name == 'RssScreen') { return <RssScreen navigator={navigator}/> } } render() { return ( <Navigator initialRoute={{name: 'StartScreen'}} renderScene={this.renderScene.bind(this)} /> ); } } AppRegistry.registerComponent('ReactNativeSampleProject', () => StartApp);
Początek kodu to import wszystkich potrzebnych komponentów z frameworka oraz zdefiniowanych przez nas widoków. Poszczególne widoki zebrałem dla porządku w katalogu screens.
W metodzie renderScene na podstawie przekazanej nazwy generujemy odpowiedni widok. Do wszystkich widoków przekazujemy obiekt navigatora, żeby również z nich można było robić przejścia.
import React, {Component} from 'react'; export default class Screen extends Component { redirect(routeName) { this.props.navigator.push({ name: routeName }); } back() { this.props.navigator.pop(); } }
Wszystkie widoki dziedziczą z tej samej klasy Screen, dzięki czemu można było wydzielić w jedno miejsce zarządzenie przejściami.
Jako menu wykorzystałem obrazki oraz abstrakcyjny komponent TouchableHighlight przechwytujący kliknięcia. Po wybraniu konkretnego obrazka wywoływana jest metoda onPress, która zwraca przygotowaną metodę z przekierowaniem do nowego widoku.
export default class StartScreen extends Screen { render() { return (<View style={styles.container}> <Image style={styles.logo} source={require('../img/logo.png')}/> <Text style={styles.title}>ReactNativeSampleProject</Text> <View style={styles.images}> <TouchableHighlight onPress={this.redirect.bind(this, 'SettingsScreen')}> <Image style={styles.image} source={require('../img/settings.png')}/> </TouchableHighlight> <TouchableHighlight onPress={this.redirect.bind(this, 'ListScreen')}> <Image style={styles.image} source={require('../img/list.png')}/> </TouchableHighlight> <TouchableHighlight onPress={this.redirect.bind(this, 'RssScreen')}> <Image style={styles.image} source={require('../img/rss.png')}/> </TouchableHighlight> </View> </View>); } }
Stylowanie aplikacji [StyleSheet]
Stylowanie aplikacji w React Native przebiega podobnie jak w zwykłych kaskadowych stylach CSS. W wypadku wielu właściwości wystarczy zamienić argumenty z myślnikiem, jak background-color, na konwencję camelCase backgroundColor.
Style można definiować inline jednak zazwyczaj lepszym pomysłem jest wydzielenie ich z kodu do stałych.
const styles = StyleSheet.create({ wrapper: { backgroundColor: '#3399ff', flex: 1, justifyContent: 'center', alignItems: 'center' }, logo: { flex: 1, width: 150, height: 150, resizeMode: 'contain' }, images: { flexDirection: 'row' }, image: { margin: 10, width: 50, height: 50, }, title: { color: 'white' } });
To rozwiązanie może nie jest tak wygodne jak wykorzystanie samych styli CSS. Jednak nawet podstawowa znajomość CSS powinna wystarczyć do w miarę bezbolesnego wykorzystania tego podejścia.
Lista danych [ListView]
Ten widok ma na celu przetestowanie długiej listy rekordów dostępnej z komponentu ListView.
export default class ListScreen extends Screen { constructor(props) { super(props); this.prepareRows(); } prepareRows() { const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1.title !== r2.title}); const images = [ require('../img/list.png'), require('../img/rss.png'), require('../img/settings.png')]; let rows = []; for (let i = 0; i < 100; i++) { let image = images[i % images.length]; rows.push({image: image, title: 'Row item number : ' + i}); } this.state = { dataSource: ds.cloneWithRows(rows), }; } render() { return ( <View> <ListView dataSource={this.state.dataSource} renderRow={(rowData) => <View style={styles.row}> <Image style={styles.image} source={rowData.image}/> <Text>{rowData.title}</Text> </View>} renderFooter={() => <View style={styles.back}> <TouchableHighlight onPress={this.back.bind(this)}> <Text>Wróć</Text> </TouchableHighlight> </View>} /> </View> ); } }
Komponent ListView umożliwia proste operowanie na przewijanej liście rekordów. Wystarczy podać źródło danych oraz zdefiniować metodę renderRow, odpowiedzialną za wygenerowanie jednego wiersza listy.
RSS – pobranie danych z Internetu
Kolejny widok aplikacji to przykład pobrania danych z Internetu oraz parsowania XML-a. Na podstawie danych pobranych z sieci (kanał RSS) budowana jest lista z wykorzystaniem ListView.
fetchData() { fetch('https://stormit.pl/feed') .then((response) => response.text()) .then((responseText) => { ... }) .done(); }
Parsowanie XML
Do sparsowania pobranego pliku XML wykorzystałem zewnętrzny komponent DOMParser, który należy doinstalować do projektu poniższą komendą.
npm install react-native-html-parser
W podobny sposób można zainstalować inne komponenty. Pokaźna lista komponentów dostępna jest w katalogu.
var DOMParser = require('react-native-html-parser').DOMParser; const domParser = new DOMParser(); const doc = domParser.parseFromString(responseText, 'text/xml'); let items = doc.getElementsByTagName('item'); let objs = [] for (var i = 0; i < items.length; i++) { const parsedDescription = domParser.parseFromString(items[i].getElementsByTagName('description')[0].textContent, "text/xml"); objs.push({ title: items[i].getElementsByTagName('title')[0].textContent, uri: items[i].getElementsByTagName('link')[0].textContent, image: this.getSmallestSrc(parsedDescription.getElementsByTagName('img')[0].getAttribute('srcset')) }) }
Podsumowanie
W moim odczuciu framework przeszedł bardzo dobrze ten test. Mimo iż jest to moja pierwsza styczność z tą technologią, nie miałem większych problemów z realizacją podstawowych funkcjonalności. Na stronie projektu można znaleźć bardzo pomocną dokumentację, która praktycznie krok po kroku wprowadza programistę w tajniki frameworka.
Powstała aplikacja działa płynnie i nie przycina się. Ciężko mieć tu jakieś zastrzeżenia, nawet przy dużej ilości danych.
Dopracowanie wyglądu samej aplikacji wymaga trochę wysiłku, ale bez większego trudu w sieci można znaleźć gotowe przykłady ostylowanych komponentów.
Podsumowując, z React Native korzysta się naprawdę dobrze, technologia stoi na wysokim poziomie, a powstała z jej pomocą aplikacja nie odbiega jakością od natywnych odpowiedników.
Jestem bardzo ciekaw, jak w tym porównaniu wyjdzie Ionic, ale już teraz widzę, że nie będzie miał łatwego zadania.
Linki
20+ BONUSOWYCH materiałów z programowania
e-book – „8 rzeczy, które musisz wiedzieć, żeby dostać pracę jako programista”,
e-book – „Java Cheat Sheet”,
checklista – „Pytania rekrutacyjne”
i wiele, wiele wiecej!