Reagujte nativní rychle:Začněte se učit nativní vývoj iOS pomocí JavaScriptu HNED!

Tato kniha je průvodcem, jak začít s vývojem React Native pro mobilní iOS. Zdrojový kód a rukopis najdete na https://github.com/azat-co/react-native-quickly. Tuto knihu si můžete přečíst online zde nebo na adrese reaktnativequickly.com nebo pokud dáváte přednost videím , můžete se podívat na videa projektu na Node.University: http://node.university/courses/react-native-quickly.

V této knize vám představím React Native pro nativní vývoj mobilních iOS a Android... a udělám to rychle. Budeme se zabývat tématy jako

  • Proč je React Native úžasný
  • Nastavení React Native Development pro iOS
  • Hello World and the React Native CLI
  • Styly a Flexbox
  • Hlavní komponenty uživatelského rozhraní React Native
  • Import modulů do projektu Xcode
  • Projekt:Časovač
  • Projekt:Aplikace Počasí

Tato kniha je o tom, jak rychle začít s Reactem, a ne o React Native, což je technicky samostatná knihovna (nebo ji někdo může dokonce nazývat framework). Ale po osmi kapitolách práce s Reactem pro vývoj webu jsem usoudil, že by bylo zábavné aplikovat naše znalosti na mobilní vývoj s využitím této úžasné knihovny. Budete překvapeni, kolik dovedností React Native již z Reactu znáte.

Vždy existuje rovnováha mezi vytvářením příkladů příliš složitými nebo příliš jednoduchými, a tedy nerealistickými a zbytečnými. V této knize se připravte na vytvoření dvou mobilních aplikací:Časovač a Počasí. Aplikace Počasí má 3 screencasty, které můžete sledovat na Node.Unversity. Provedou vás aplikací Počasí.

Zdrojový kód projektů (stejně jako rukopis k odeslání problémů/chyb) je v https://github.com/azat-co/react-native-quickly repository. Užijte si to!

Proč je React Native úžasný

Aplikace React Native nejsou stejné jako hybridní nebo takzvané HTML5 aplikace. Pokud nejste obeznámeni s hybridním přístupem, je to, když existuje web zabalený v bezhlavém prohlížeči. Bezhlavý prohlížeč je zobrazení prohlížeče bez řádku URL nebo navigačních tlačítek. Vývojáři v podstatě vytvářejí responzivní webové stránky pomocí běžných webových technologií, jako je JavaScript, HTML a CSS, a možná i framework jako jQuery Mobile, Ionic, Ember nebo Backbone. Poté jej zabalí jako nativní aplikaci společně s tímto bezhlavým prohlížečem. Nakonec můžete znovu použít stejnou kódovou základnu napříč platformami, ale zkušenosti s používáním hybridních aplikací často chybí. Obvykle nejsou tak svižné nebo postrádají určité funkce ve srovnání s nativními aplikacemi. Mezi nejoblíbenější frameworky pro hybridní aplikace patří Sencha Touch, Apache Cordova, PhoneGap a Ionic.

Na druhou stranu aplikace React Native není web zabalený do bezhlavého prohlížeče. Je to nativní kód Objective C nebo Java, který komunikuje s JavaScriptem React. To umožňuje následující výhody oproti nativnímu vývoji:

  • Opětovné načtení horké/živé. Vývojáři mohou své aplikace znovu načítat, aniž by je museli znovu kompilovat, což urychluje vývoj a eliminuje potřebu složitých editorů a IDE WYSIWYG (What You See Is What You Get).
  • Systém rozložení Flexbox. Jedná se o syntetizovaný systém pro rozvržení, který je podobný CSS a umožňuje vývoj napříč platformami.
  • Ladění v prohlížeči Chrome. Vývojáři mohou používat již známé nástroje DevTools.
  • Napište jednou a nechte to fungovat na různých platformách.
  • Port z webu Snadno reagujte, například pomocí frameworků jako ComponentKit.
  • Využijte obrovské množství open-source nástrojů, utilit, knihoven, znalostí, osvědčených postupů, ES6/7+ a knih o JavaScriptu (nejoblíbenější programovací jazyk na světě).
  • Používejte nativní prvky, které jsou lepší a výkonnější než webová technologie (přístup HTML5/wrapper).
  • Reagovat. Žádné specifické datové vázání, správa událostí nebo mikrospráva pohledů, to vše má tendenci zvyšovat složitost. React používá deklarativní přístup a jednoduchý jednosměrný tok dat.

Z těchto důvodů není překvapením, že velké i malé společnosti naskakují na vlak React Native a opouštějí hybridní i nativní přístupy. Každý den čtu příspěvky na blogu, že ta a ta společnost nebo nějaký iOS vývojář přešel na React Native a jak jsou s tímto krokem spokojeni. Jste připraveni začít s tím, co se zdá být příští generací mobilního vývoje?

Nastavení React Native Development

Tato kapitola se zabývá pouze vývojem React Native pro iOS. Budu používat pouze univerzální multiplatformní komponenty – například Navigator a ne NavigatorIOS – poskytnutý kód by tedy měl fungovat i pro Android. Nebudu však zacházet do podrobností o tom, jak byste kompilovali projekty Android.

Pokud nepracujete na hardwaru Apple s Mac OS X, můžete si nainstalovat virtuální počítač s Mac OS X na OS Linux nebo Windows podle této příručky. Dopředu předpokládám, že všichni pracujeme na Mac OS X, ať už virtuálním nebo ne, na vytváření aplikací pro iOS.

Chcete-li vše nainstalovat, můžete to udělat ručně nebo použít správce balíčků. Vzhledem k tomu, že pracujeme v prostředí Mac OS X, doporučuji k instalaci některých požadovaných nástrojů použít Homebrew (a.k.a. brew). Pokud Homebrew ještě nemáte, můžete přejít na jeho webovou stránku http://brew.sh nebo spustit tento příkaz Ruby (Mac OS X je dodáván s Ruby):

$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Budeme potřebovat následující nástroje a knihovny:

  • Node.js v5.1 a npm v3.3.12 – Pokud jste četli kapitoly 1–8, měli byste je již mít. Pokud jste skočili přímo sem, postupujte podle pokynů v příloze B.
  • Watchman v4.1.0 – Tento nástroj bude sledovat a aktualizovat změny v souboru zdrojového kódu. Použijte $ brew install [email protected] jej nainstalovat.
  • Google Chrome – Prohlížeč vám umožní ladit aplikace React Native během vývoje. Zde je odkaz ke stažení.
  • React Native CLI v0.1.7 – Tento nástroj vám umožní vytvářet standardní verze pro vaše aplikace React Native. Nainstalujte jej pomocí $ npm install -g [email protected] .
  • Xcode v7.2 – IDE, kompilátory a sady SDK pro iOS, OS X, tvOS a watchOS. Chcete-li jej nainstalovat, kliknutím na odkaz na adrese https://developer.apple.com/xcode/download otevřete Mac App Store.
  • Flow – kontrola statického typu pro JavaScript. Chcete-li jej nainstalovat pomocí Homebrew, spusťte $ brew install [email protected] .

Doporučuji použít NVM v0.29.0, n nebo podobný správce verzí Node. Tento krok je volitelný, ale doporučený, protože znamená, že můžete přejít na Node.js v5.1, i když je vaše hlavní verze novější. Chcete-li použít Homebrew, spusťte $ brew install nvm a postupujte podle pokynů.

Váš systém by měl být připraven na vývoj aplikací pro iOS. Začněme typickým příkladem programování, Hello World.

[Sidenote]

Čtení blogových příspěvků je dobré, ale sledování videokurzů je ještě lepší, protože jsou poutavější.

Mnoho vývojářů si stěžovalo, že na Node je nedostatek dostupného kvalitního videomateriálu. Sledování videí na YouTube je rušivé a platit 500 $ za videokurz Node je šílené!

Jděte se podívat na Node University, která má na Node ZDARMA videokurzy:node.university.

[Konec vedlejší poznámky]

Hello World and the React Native CLI

Nejprve přejděte do složky, kde chcete mít svůj projekt. Můj je /Users/azat/Documents/Code/react/ch9/. Poté spusťte $ react-native init terminálový příkaz k zahájení projektu vytvořením projektů pro iOS a Android, package.json a dalších souborů a složek:

$ react-native init hello

Počkejte. Může to chvíli trvat. V tuto chvíli se děje několik věcí. Je zřejmé, že je vytvořena složka ahoj. Poté nástroj vytvoří soubor package.json. (Miluji, jak jsou dnes Node a npm všude. V roce 2012 tomu tak nebylo!) V package.json je react-native CLI, které je globální, vkládá lokální závislost react-native . Je to podobné jako při spuštění $ npm i react-native --save .

Po tomto kroku globální react-native CLI spouští místní kód z hello/node_modules/react-native/local-cli/cli.js a ten zase spustí pomocný bash skript hello/node_modules/react-native/init.sh . Tento bash skript vytváří lešení s kódem React Native v souborech index.ios.js a index.android.js a také projekty iOS a Android ve složkách ios a android.

Ve složce ios nástroj vytvoří soubory projektu Xcode s kódem Objective C. To je naše zaměření právě teď. Zde je základní struktura složek vytvořená nástrojem:

/android
  /app
  /gradle
  - build.gradle
  - gradle.properties
  - gradlew
  - gradlew.bat
  - settings.gradle
/ios
  /hello
  /hello.xcodeproj
  /helloTests
/node_modules
  - ...
- index.android.js
- index.ios.js
- package.json
- .watchman.config
- .flowconfig

Jakmile je vše vygenerováno, vrátíte se do příkazového řádku. Výstup na mém počítači byl tento, který mi dokonce říká, jak spouštět aplikace:

To run your app on iOS:
   Open /Users/azat/Documents/Code/react/ch9/hello/ios/hello.xcodeproj in Xcode
   Hit the Run button
To run your app on Android:
   Have an Android emulator running (quickest way to get started), or a device connected
   cd /Users/azat/Documents/Code/react/ch9/hello
   react-native run-android

Máte dvě možnosti. Můžete ručně otevřít Xcode a vybrat Otevřít (Command+O) z nabídky Soubor, otevřít soubor hello.xcodeproj a kliknutím na černý obdélník sestavit a spustit. Nebo můžete přejít do složky pomocí $ cd hello , spusťte $ open ios/hello.xcodeproj a klikněte na „play“ v Xcode pro vytvoření a spuštění.

Pokud jste postupovali podle kroků správně, zobrazí se nové okno terminálu s nápisem React Packager. Začíná zprávou:

 ~/Documents/Code/react/ch9/hello/node_modules/react-native/packager ~
 ┌────────────────────────────────────────────────────────────────────────────┐
 │  Running packager on port 8081.                                            │
 │                                                                            │
 │  Keep this packager running while developing on any JS projects. Feel      │
 │  free to close this tab and run your own packager instance if you          │
 │  prefer.                                                                   │
 │                                                                            │
 │  https://github.com/facebook/react-native                                  │
 │                                                                            │
 └────────────────────────────────────────────────────────────────────────────┘
Looking for JS files in
   /Users/azat/Documents/Code/react/ch9/hello

[12:15:42 PM] <START> Building Dependency Graph
[12:15:42 PM] <START> Crawling File System
[12:15:42 PM] <START> Loading bundles layout
[12:15:42 PM] <END>   Loading bundles layout (0ms)

tak co se tu děje? React Native zabalí naše soubory JavaScriptu React Native a poskytuje je na localhost:8081. Je to tak, je to jako jakýkoli jiný webový server, pokud otevřete prohlížeč na adrese http://localhost:8081/index.ios.bundle?platform=ios&dev=true. Otevřete jej nyní ve svém prohlížeči. Hledejte „ahoj“. Uvidíte kód React Native sdružený do jednoho velkého souboru. To by mělo znít povědomě většině webových vývojářů.;-)

Kde jsem získal http://localhost:8081/index.ios.bundle?platform=ios&dev=true URL? Je to v souboru hello/ios/hello/AppDelegate.m na řádku 34 (používáte stejnou verzi jako já, že?):

jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];

Kód Objective C získává JavaScript ze serveru. To je výchozí možnost číslo jedna. Existuje druhá možnost, která je v tuto chvíli komentována. Přebírá kód JavaScript ze statického souboru (řádek 42 ve stejném souboru). Je dobré mít možnosti!

Komentáře nám říkají, jak můžeme server roztočit. Je to jen $ npm start příkaz, který spustí $ react-native start , takže můžeme použít i to druhé. Pokud tedy přepínáte mezi projekty nebo nechcete používat terminálový proces automaticky otevřený Xcode, můžete vždy spustit nový server. Jen mějte na paměti, jako u každého jiného serveru, že nemůžete mít dva naslouchající na stejném portu. Proto před spuštěním nového serveru na localhost:8081 ukončete starý proces.

Spuštění okna Simulátoru nějakou dobu trvá. Preferuji práci s iPhone 6, ne iPhone 6 Plus. Na obrazovce tak mám více prostoru pro vývoj. Nyní byste měli mít otevřené okno Simulátoru. Prohrábněte se. Není toho moc k vidění, jak ukazuje obrázek 1.

Pokračujte a otevřete soubor index.io.js. Můžete vidět známý kód JavaScript/Node. Pokud vám ještě ES6 (nebo ES2015 – oficiální název) nevyhovuje, podívejte se na kapitolu 10 a přílohu I.

Na začátku souboru je příkaz destructuring pro import objektů z React Native:

var React = require('react-native');
var {
  AppRegistry,
  StyleSheet,
  Text,
  View,
} = React;

Dále můžete vidět svého starého dobrého přítele React.createClass() s render metoda:

var hello = React.createClass({
    render: function() {
        return (
            <View style={styles.container}>
                <Text style={styles.welcome}>
                  Welcome to React Native!
                </Text>
                <Text style={styles.instructions}>
                  To get started, edit index.ios.js
                </Text>
                <Text style={styles.instructions}>
                    Press Cmd+R to reload,{'\n'} Cmd+D or shake for dev menu
                </Text>
             </View>
        );
    }
});

Sakra, s dobrými komentáři, jako je tento, brzy skončím – to znamená, že nebudu muset psát knihy.;-) Jak se říká, stisknutím Command+R v Simulátoru jej znovu načtete. Pokračujte a změňte „Welcome to React Native!“ na "Ahoj světe!" Uložte index.ios.js a znovu načtěte aplikaci v okně Simulátoru.

Poznámka:Pokud používáte nějaké nestandardní rozložení klávesnice jako Dvorak nebo Colemak (jako já), v okně Simulátoru budete muset použít standardní americké rozložení pro zkratky a také pro psaní textu.

Sledujte změny a všimněte si, že jsme nemuseli přestavovat projekt Xcode. Watchman aktualizoval balíček poté, co jsme soubor uložili. Nový kód byl doručen na server na localhost:8081. Můžete vidět text "Ahoj světe!" v prohlížeči, pokud přejdete na http://localhost:8081/index.ios.bundle?platform=ios&dev=true. Jakmile jsme znovu načetli simulátor, nový kód tam byl!

V index.ios.js jsou ještě dvě zajímavé věci (a pak přejdeme k prozkoumání každé komponenty samostatně):StyleSheet a AppRegistry . Nejsou ve webovém Reactu, tak mi je dovolte vysvětlit.

Styly a Flexbox

První je způsob, jak vytvořit rozvržení, styly a formátování v prvcích. Vytvoříme objekt s StyleSheet.create() . Toto jsou například naše styly Hello World:

var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

Doufám, že uhodnete význam některých vlastností, například backgroundColor a fontSize . Jsou podobné background-color a font-size v CSS a už víte, že React používá místo pomlček camelCase. Další vlastnosti stylu, například flex , jsou specifické pro React Native. Toto znamenají:

  • flex
  • justifyContent
  • alignItems TK
  • flexDirection

Čísla ve vlastnostech stylu jsou body, nikoli obrazové body. Rozdíl je v tom, že body mohou znamenat 1 nebo více pixelů v závislosti na obrazovce, takže použití bodů osvobozuje vývojáře od psaní if/else podmínky pro různé formáty obrazovky. Nejpozoruhodnější je, že na starých iPhonech, jako je iPhone 3GS, je 1 bod 1 pixel (1:1). Na druhou stranu u nových iPhonů s obrazovkami Retina, jako je iPhone 6, je 1 bod čtverec 2×2 pixelů (1:2).

Poslední příkaz v souboru je podobný ReactDOM.render() z webu React development:

AppRegistry.registerComponent('hello', () => hello);

Registruje naši komponentu hello , v registru. Název ve funkci tlusté šipky (druhý argument) můžete změnit na cokoliv jiného, ​​ale neměňte první argument. Tukové šipky ES6 jsou popsány v kapitole 10 a dodatku I. Právě teď podrobněji prozkoumáme komponenty React Native.

Hlavní součásti uživatelského rozhraní React Native

Možná jste si toho všimli v render používáme speciální značky/prvky jako <View> a <Text> místo <div> nebo <p> . Tyto speciální prvky nebo komponenty React Native pocházejí z react-native knihovna. Je jich tam celá hromada a jsem si jistý, že brzy přibudou další. Existují komponenty specifické pro iOS a Android a také syntetické, které by fungovaly napříč platformami. Komponenty pouze pro iOS mají obvykle IOS na konci jejich názvu (například NavigatorIOS ), zatímco univerzální multiplatformní komponenty takové koncovky nemají (například Navigator ).

Popis všech komponent React Native by vydal na knihu. Také, jak jsem již řekl, sami vývojáři komunity a Facebooku neustále a neúnavně přidávají nové komponenty a aktualizují ty stávající. Úplný aktuální seznam podporovaných součástí je lepší nahlédnout do oficiální dokumentace. Abyste však mohli vyvíjet minimální mobilní aplikace s React Native, budete se muset naučit hlavní (podle mého názoru) komponenty. Jsou to:

  • View —Základní pohledová komponenta. Každý render musí mít alespoň prázdný View .
  • Text —Textová složka. Veškerý text musí být zabalen do této komponenty, na rozdíl od textu ve webovém Reactu.
  • TextInput —Složka vstupního pole formuláře. Použijte jej k zachycení uživatelského vstupu.
  • ScrollView — Zobrazit s rolovatelným obsahem. Použijte jej, když se váš obsah nevejde na jednu obrazovku.
  • ListView —Zobrazení se strukturovanými daty. Použijte jej k výstupu seznamů nebo tabulek.
  • TouchableHighlight —Uživatelská dotyková komponenta. Použijte jej k zachycení událostí uživatelského dotyku, podobně jako kotevní značky při vývoji webu.
  • Switch —Booleovský spínač zapnutí/vypnutí. Použijte jej pro nastavení a formuláře.
  • Navigator —Vysoce přizpůsobitelná navigační komponenta. Použijte jej k navigaci mezi obrazovkami a implementaci navigačního panelu a/nebo navigačního panelu s drobečkovou navigací.

Všechny tyto komponenty byly vybrány, protože jejich znalost vám poskytne naprosté minimum k vytvoření poněkud užitečných aplikací, jak uvidíte v projektech Timer a Weather App. Tyto komponenty jsou také univerzální; to znamená, že je můžete (a měli byste) používat pro iOS a Android. Možná dokonce můžete použít stejnou kódovou základnu pro index.ios.js a index.android.js.

V této části knihy použiji úryvky kódu z projektů Timer a Weather App, aby byly příklady realističtější než jen některé foo-bary. Kód pro časovač je v timer . Kód aplikace Počasí je v weather .

Zobrazit

Jak jsem již zmínil, View je nejzákladnější složkou. Pokud nevíte, co použít, použijte View . Do View můžete zabalit několik dalších komponent , podobně jako je zabalíte do <div> , protože render() musí vrátit pouze jeden prvek . Chcete-li například uvést počet zbývajících sekund a štítek pod ním, zabalte je do View :

var Timer = React.createClass({
   render() {
     // ...
     return (
       <View>
         <Text style={styles.heading}>{this.props.time}</Text>
         <Text>Seconds left</Text>
       </View>
     )
    }
})

Text

Text komponenta slouží k vykreslování textu. Stejně jako většinu ostatních komponent jej můžeme dodat se styly. Například toto Text prvek používá Flex a má velikost písma 36, ​​odsazení nad 40 a okraj 10:

var TimerWrapper = React.createClass({
  // ...
  render() {
    return (
      <ScrollView>
        <View style={styles.container}>
          <Text style={styles.heading}>Timer</Text>
         ...
        </View>
      </ScrollView>
    )
  }
})

var styles = StyleSheet.create({
  ...
  heading: {
    flex: 1,
    fontSize: 36,
    paddingTop: 40,
    margin: 10
  },
  ...
})

Výsledek je znázorněn na obrázku 1.

V style můžeme pohodlně kombinovat dva nebo více objektů stylu vlastnost pomocí pole. Například toto Text element používá styly z navBarText a navBarButtonText :

        <Text style={[styles.navBarText, styles.navBarButtonText, ]}>
          {'<'} {previousRoute.name}
        </Text>

style atribut a kombinace stylů nejsou exkluzivní pro Text . Můžete je použít na jiné komponenty.

TextInput

TextInput je komponenta vstupního pole. Obvykle byste jej používali ve formulářích k zachycení uživatelského vstupu, jako je e-mailová adresa, heslo, jméno atd. Tato komponenta má některé známé vlastnosti, jako například:

  • placeholder —Ukázkový text, který se zobrazí, když je hodnota prázdná
  • value —Hodnota vstupního pole
  • style —Atribut stylu

Další atributy jsou specifické pro React Native. Hlavní jsou:

  • enablesReturnKeyAutomatically —Pokud false (výchozí hodnota), zabrání uživateli v odeslání prázdné textové hodnoty tím, že zakáže návratový klíč.
  • onChange —Metoda, kterou lze vyvolat při změně hodnoty. Předá objekt události jako argument.
  • onChangeText —Metoda, kterou lze vyvolat při změně hodnoty. Předá textovou hodnotu jako argument.
  • onEndEditing —Metoda, která se vyvolá, když uživatel stiskne klávesu Return na virtuální klávesnici.
  • multiline —Pokud true (výchozí je false ), pole může mít více řádků.
  • keyboardType —Jedna z hodnot enumerátoru, například 'default' , 'numeric' nebo 'email-address' .
  • returnKeyType —Enumerátor pro návratový klíč, například 'default' , 'go' , 'google' , 'join' , 'next' , 'route' , 'search' , 'send' , 'yahoo' , 'done' nebo 'emergency-call' . Pouze iOS.

Úplný seznam aktuálních vlastností pro TextInput pro iOS a Android je na https://facebook.github.io/react-native/docs/textinput.html#props.

Zvažte tento příklad, který vykresluje vstupní pole názvu města pomocí obslužné rutiny this.search . Tlačítko na klávesnici bude říkat Hledat, hodnota je přiřazena stavu (řízená komponenta!) a zástupný symbol je San Francisco:

  <TextInput
    placeholder="San Francisco"
    value={this.state.cityName}
    returnKeyType="search"
    enablesReturnKeyAutomatically={true}
    onChangeText={this.handleCityName}
    onEndEditing={this.search}
    style={styles.textInput}/>

Výsledek je znázorněn na obrázku 2, kde můžete na virtuální klávesnici pozorovat klávesu Search.

S onChangeText vlastnost, získáme hodnotu vstupního pole jako argument funkce handleru (handleCityName(event) ). Například pro zpracování názvu města a nastavení stavu cityName v řízené komponentě musíme implementovat handleCityName takhle:

  ...
  handleCityName(cityName) {
    this.setState({ cityName: cityName})
  },
  ...

Na druhou stranu, pokud potřebujete víc než text, je tu onChange . Když událost přijde na onChange funkce handleru, event argument má vlastnost nazvanou nativeEvent a tato vlastnost má zase vlastnost nazvanou text . Můžete implementovat onChange takto:

...
onNameChanged: function(event) {
  this.setState({ name: event.nativeEvent.text });
},
...
render() {
  return (
    <TextInput onChange={this.onNameChange} ... />
  )
}
})

ScrollView

Toto je vylepšená verze View komponent. Umožňuje posouvání obsahu, takže můžete posouvat nahoru a dolů pomocí dotykových gest. To je užitečné, když se obsah nevejde na jednu obrazovku. Například mohu použít ScrollView jako kořenový adresář mého render() protože vím, že timerOptions může být velmi velké pole, takže vykresluje mnoho řádků dat (Button komponenty):

var TimerWrapper = React.createClass({
  // ...
  render() {
    return (
      <ScrollView>
        <View style={styles.container}>
          <Text style={styles.heading}>Timer</Text>
          <Text style={styles.instructions}>Press a button</Text>
          <View style={styles.buttons}>
            {timerOptions.map((item, index, list)=>{
              return <Button key={index} time={item} startTimer={this.startTimer} isMinutes={this.state.isMinutes}/>
            })}
          </View>
          ...
        </View>
      </ScrollView>
    )
  }
})

Zobrazení seznamu

ListView je pohled, který vykresluje seznam řádků z poskytnutých dat. Ve většině případů chcete zabalit ListView v ScrollView . Data musí být v určitém formátu. Použijte dataSource = new ListView.DataSource() k vytvoření objektu zdroje dat, pak použijte dataSource.cloneWithRows(list) k naplnění zdroje dat daty ze standardního pole JavaScriptu.

Zde je příklad. Nejprve vytvoříme objekt zdroje dat:

let dataSource = new ListView.DataSource({
  rowHasChanged: (row1, row2) => row1 !== row2
})

Potom použijeme cloneWithRows metoda k vyplnění dat z pole, response.list :

this.props.navigator.push({
  name: 'Forecast',
  component: Forecast,
  passProps: {
    forecastData: dataSource.cloneWithRows(response.list),
    forecastRaw: response
  }
})

Ignorujte navigator zatím zavolejte. To se objeví později v kapitole.

Máme data, takže nyní vykreslíme ListView poskytnutím vlastností dataSource a renderRow . Toto je například seznam informací o předpovědi, přičemž každý řádek představuje předpověď na určitý den. ListView rodič je ScrollView :

module.exports = React.createClass({
  render: function() {
    return (
      <ScrollView style={styles.scroll}>
        <Text style={styles.text}>{this.props.forecastRaw.city.name}</Text>
        <ListView dataSource={this.props.forecastData} renderRow={ForecastRow} style={styles.listView}/>
      </ScrollView>
    )
  }
})

Jak můžete hádat, renderRow , což je ForecastRow v tomto příkladu je další komponenta, která je zodpovědná za vykreslení jednotlivé položky z poskytnutého zdroje dat. Pokud neexistují žádné metody nebo stavy, můžete vytvořit bezstavovou komponentu (více o bezstavových komponentách v kapitole 10). V ForecastRow , vypíšeme datum (dt_txt ), popis (description ) a teplotu (temp ):

const ForecastRow = (forecast)=> {
  return (
    <View style={styles.row}>
      <View style={styles.rightContainer}>
        <Text style={styles.subtitle}></Text>
        <Text style={styles.subtitle}>
          {forecast.dt_txt}: {forecast.weather[0].description}, {forecast.main.temp}
        </Text>
       </View>
    </View>
  )
}

Můžete dosáhnout funkčnosti ListView s jednoduchým Array.map() postavit. V tomto případě není potřeba zdroj dat.

TouchableHighlight

TouchableHighlight zachycuje události dotyku uživatele. Vývojáři implementují tlačítka podobná kotevním prvkům (<a> ) tagy při vývoji webu. Akce je předána jako hodnota onPress vlastnictví. Abychom mohli implementovat tlačítko, musíme do něj také vložit nějaký text.

Toto je například tlačítko, které spouští startTimer a obsahuje text, který se skládá z time vlastnost a buď slovo „minuty“ nebo „sekundy“:

var Button = React.createClass({
  startTimer(event) {
    // ...
  },
  render() {
    return (
      <TouchableHighlight onPress={this.startTimer}>
        <Text style={styles.button}>{this.props.time} {(this.props.isMinutes) ? 'minutes' : 'seconds'}</Text>
      </TouchableHighlight>
    )
  }
})

Styl TouchableHighlight samo o sobě není nic; z tohoto důvodu při implementaci tlačítek buď stylizujeme text uvnitř TouchableHighlight (Obrázek 3) nebo použijte obrázek s Image komponenta.

Podobné komponenty jako TouchableHighlight jsou:

  • TouchableNativeFeedback
  • TouchableOpacity
  • TouchableWithoutFeedback

Přepnout

Pravděpodobně jste viděli a používali Switch komponentu nebo podobný nativní prvek mnohokrát. Vizuální příklad je znázorněn na obrázku 9-X. Je to malý přepínač, který není nepodobný zaškrtávacímu políčku. Toto je booleovský vstupní prvek zapnutí/vypnutí, který se hodí v nastavení formulářů a aplikací.

Při implementaci Switch , poskytnete alespoň dvě vlastnosti, onValueChange a value (opět řízená součástka!). Tento přepínač například způsobí, že aplikace uloží název města nebo ne:

      ...
        <Text>Remember?</Text>
        <Switch onValueChange={this.toggleRemember} value={this.state.isRemember}></Switch>
      ....

V obslužné rutině toggleRemember , stav jsem nastavil na hodnotu, která je opakem aktuálního this.state.isRemember :

  // ...
  toggleRemember() {
    this.setState({ isRemember: !this.state.isRemember}, ()=>{
      // Remove the city name from the storage
      if (!this.state.isRemember) this.props.storage.removeItem('cityName')
    })
  },
  // ...

Navigátor

Navigator je vysoce přizpůsobitelný navigační komponent umožňující navigaci mezi obrazovkami v aplikaci. Můžeme jej použít k implementaci navigační lišty a/nebo drobečkové navigace. Navigační lišta je nabídka v horní části obrazovky s tlačítky a nadpisem.

Je zde také NavigatorIOS , kterou Facebook nepoužívá, a tudíž ji komunita oficiálně nepodporuje a neudržuje. NavigatorIOS má vestavěnou navigační lištu, ale funguje pouze pro vývoj iOS. Další nevýhodou je, že NavigatorIOS neobnoví trasy/obrazovky, když se změní vlastnosti těchto tras. Naopak Navigator lze použít na iOS a Android a obnovuje trasy při změně vlastností, které jim byly předány. Navigační panely si můžete přizpůsobit podle svých představ.

Protože Navigator je flexibilní, našel jsem několik způsobů, jak jej implementovat. Existuje metoda, kde máte zásobník tras a poté navigujete pomocí ID tras a metod vpřed/zpět. Rozhodl jsem se pro tento vzor, ​​který používá abstrakci a NavigatorIOS rozhraní (passProps ). Řekněme App komponenta je ta, kterou registrujete pomocí AppRegistry . Potom chcete vykreslit Navigator v App render metoda:


const App = React.createClass({
  render() {
    return (
      <Navigator
        initialRoute={{
          name: 'Search',
          index: 0,
          component: Search,
          passProps: {
            storage: storage
          }
        }}
        ref='navigator'
        navigationBar={
          <Navigator.NavigationBar
            routeMapper={NavigationBarRouteMapper}
            style={styles.navBar}
          />
        }
        renderScene={(route, navigator) => {
          let props = route.passProps
          props.navigator = navigator
          props.name = route.name
          return React.createElement(route.component, props)
        }}
      />
    )
  }
})

Můžete pozorovat několik atributů Navigator :

  • initialRoute —Úplně první route objekt, který renderujeme.
  • ref —Vlastnost App prvek, který bude mít Navigator objekt. Můžeme jej použít ke skoku do nových scén.
  • navigationBar —Horní nabídka s názvem a levým a pravým tlačítkem.
  • renderScene —Metoda, která je spuštěna při navigační události pro každou trasu. Dostaneme route objekt a vykreslit komponentu pomocí route.component a route.passProps .

Chcete-li přejít na novou obrazovku, jako je Forecast (Forecast komponenta) a předejte mu vlastnosti, vyvolejte navigator.push() :

        // ...
        this.props.navigator.push({
          name: 'Forecast',
          component: Forecast,
          passProps: {
            forecastData: dataSource.cloneWithRows(response.list),
            forecastRaw: response
          }
        })
        // ...

V tomto příkladu předávám komponentu a rekvizity s každým push() volání. Pokud používáte zásobník tras, což je v podstatě seznam komponent, můžete předat pouze ID nebo název komponenty, nikoli celý objekt, a získat objekt ze zásobníku. Jako obvykle existuje více než jeden způsob, jak stáhnout sumce z kůže.

Import modulů do projektu Xcode

Co když chcete použít komunitní komponentu React Native, tj. něco, co není součástí react-native , ale je poskytován jako samostatný modul npm? Modul můžete importovat do svého projektu!

V Timer potřebujeme přehrát zvuk, když čas vypršel. V době psaní tohoto článku (leden 2016) neexistuje žádná oficiální komponenta pro zvuky, ale existuje několik uživatelských modulů. Jedním z nich je react-native-audioplayer . Nejprve jej nainstalujte pomocí npm ve složce projektu:

$ npm install [email protected] --save

Momentálně se zaměřujeme na iOS, takže instalace je následující:

  1. Otevřete svůj projekt v Xcode.
  2. V Xcode najděte Project Navigator na levém postranním panelu.
  3. V Navigátoru projektu klikněte pravým tlačítkem na Knihovny.
  4. V kontextové nabídce klikněte na Přidat soubory do „časovače“. (V případě potřeby nahraďte „časovač“ jiným názvem projektu.)
  5. Přejděte na node_modules/react-native-audioplayer . Přidejte soubor RNAudioPlayer.xcodeproj . Výsledek je znázorněn na obrázku 5.
  1. V Navigátoru projektu vyberte svůj projekt (časovač).
  2. Klikněte na cíl sestavení pro časovač v seznamu Cíle (obrázek 9-X).
  1. Kliknutím na kartu Fáze sestavení ji otevřete.
  2. Rozbalte položku Link Binary With Libraries kliknutím na ni.
  3. Klikněte na tlačítko plus (+) a přidejte libRNAudioPlayer.a pod Workspace, nebo jednoduše přetáhněte libRNAudioPlayer.a z Navigátoru projektu. Je pod Libraries/RNAudioPlayer.xcodeproj/Products .
  4. Spusťte svůj projekt (stiskněte Command+R nebo klikněte na černý obdélník označující „přehrát“).

Pokud jste vše udělali správně, v souboru index.ios.js můžete importovat modul s require() :

AudioPlayer = require('react-native-audioplayer')

A přehrajte zvuk pomocí play() :

AudioPlayer.play('flute_c_long_01.wav')

Zvukový soubor musí být součástí balíčku. Chcete-li tak učinit, vyberte Kopírovat zdroje balíčku a přidejte flute_c_long_01.wav , nebo svůj vlastní zvukový soubor, jak je znázorněno na obrázku 7.

To je celá příprava. Nyní můžeme implementovat časovač!

Projekt:Časovač

Viděli jste kousky z aplikace Timer (obrázek 8), která je v timer . Myslím, že bude přínosné, když projdeme realizací najednou. Hlavní soubor je index.ios.js . Má tři komponenty, ne nepodobné mému prohlížeči/webu React Timer od React Quickly (Manning, 2016), (GitHub):

  • TimerWrapper —Inteligentní komponenta, která má většinu logiky pro časovač
  • Timer —Němá komponenta, která přehrává zvuk, když vyprší čas, a zobrazuje počet zbývajících sekund
  • Button —Komponenta, která zobrazuje tlačítko a spouští začátek odpočítávání vyvoláním ovladače, který mu předal rodič (TimerWrapper )

Spustíme soubor index.ios.js s importy React Native, jeho objektů a Audio Player:

'use strict'

var React = require('react-native'),
  AudioPlayer = require('react-native-audioplayer')

var {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  ScrollView,
  TouchableOpacity,
  Switch
} = React

Další příkaz deklaruje pole možností pro tlačítka časovače, které pomocí Switch změníme buď na počet sekund, nebo na počet minut. :

const timerOptions = [5, 7, 10, 12, 15, 20]

Vylepšil jsem TimerWrapper z projektu kapitoly 5 s dynamickým generováním tlačítek a přepínáním sekund na minuty. Přepínač používá isMinutes stav, takže jej nastavíme na false na začátku. Jen pro připomenutí, tento příklad používá syntaxi ES6+/ES2015+. Pokud ji neznáte nebo si nejste jisti, zda ji znáte, prostudujte si kapitolu 10 a přílohu I.

var TimerWrapper = React.createClass({
  getInitialState () {
    return {time: null, int: null, isMinutes: false}
  },

Počáteční hodnota isMinutes je false . toggleTime je manipulátor pro Switch . Překlopíme hodnotu isMinutes s logickým ne (! ). Je důležité nastavit čas na null , protože jinak se zvuk spustí pokaždé, když přepneme přepínač. Přehrávání zvuku je podmíněno time je 0 , takže pokud jej nastavíme na null , nebude hrát. Zvuková logika je v Timer komponent. Algoritmus React se rozhodne jej znovu vykreslit, když změníme stav isMinutes :

  toggleTime(){
    let time = this.state.time
    if (time == 0 ) time = null
    this.setState({isMinutes: !this.state.isMinutes, time: time})
  },

Další metoda spustí časovače. Pokud jste postupovali podle projektu v kapitole 5, víte, jak to funguje. React Native poskytuje API pro časovače, tj. clearInterval() a setInterval() jako globální objekty. Číslo v time stav je vždy v sekundách, i když na tlačítkách vidíme minuty a spínač je zapnutý:

  startTimer(time) {
    clearInterval(this.state.int)
    var _this= this
    var int = setInterval(function() {
      console.log('2: Inside of setInterval')
      var tl = _this.state.time - 1
      if (tl == 0) clearInterval(int)
      _this.setState({time: tl})
    }, 1000)
    console.log('1: After setInterval')
    return this.setState({time: time, int: int})
  },

V render používáme jednoduchý map() iterátor pro vygenerování sloupce tlačítek. Je zabaleno do ScrollView , takže se s timerOptions můžete opravdu zbláznit pole přidáním dalších prvků a podívejte se, co se stalo:

  render() {
    return (
      <ScrollView>
        <View style={styles.container}>
          <Text style={styles.heading}>Timer</Text>
          <Text style={styles.instructions}>Press a button</Text>
          <View style={styles.buttons}>
            {timerOptions.map((item, index, list)=>{
              return <Button key={index} time={item} startTimer={this.startTimer} isMinutes={this.state.isMinutes}/>
            })}
          </View>

Za tlačítky máme textový štítek, který říká Minuty a Switch ovládaná součást:

          <Text>Minutes</Text>
          <Switch onValueChange={this.toggleTime} value={this.state.isMinutes}></Switch>
          <Timer time={this.state.time}/>
        </View>
      </ScrollView>
    )
  }
})

Tlačítka vykreslujeme v TimerWrapper pocházejí z této složky. Má ternární podmínku (aka Elvisův operátor) pro nastavení buď minut, jejich vynásobením 60 (60 sekund za minutu), nebo sekund:

var Button = React.createClass({
  startTimer(event) {
    let time = (this.props.isMinutes) ? this.props.time*60 : this.props.time
    return this.props.startTimer(time)
  },

Při vykreslování používáme TouchableOpacity , který je funkčně podobný TouchableHighlight liší se však vizuální reprezentací (při dotyku je průhledná). Existuje ternární podmínka pro výstup slova „minuty“ nebo „sekundy“ na základě hodnoty isMinutes vlastnost:

  render() {
    return (
      <TouchableOpacity onPress={this.startTimer}>
        <Text style={styles.button}>{this.props.time} {(this.props.isMinutes) ? 'minutes' : 'seconds'}</Text>
      </TouchableOpacity>
    )
  }
})

Timer komponenta vykreslí počet zbývajících sekund a také přehraje zvuk, když je toto číslo 0 :

var Timer = React.createClass({
   render() {
     if (this.props.time == 0) {
      AudioPlayer.play('flute_c_long_01.wav')
     }
     if (this.props.time == null || this.props.time == 0) return <View><Text  style={styles.heading}> </Text></View>
     return (
       <View>
         <Text style={styles.heading}>{this.props.time}</Text>
         <Text>Seconds left</Text>
       </View>
     )
    }
})

styles objekt používá Flex. V container , je zde flexDirection , nastavte na column . It positions elements vertically, as in a column. Another value is row , which will position them horizontally.

var styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
    alignItems: 'center'
  },
  heading: {
    flex: 1,
    fontSize: 36,
    paddingTop: 40,
    margin: 10
  },
  instructions: {
    color: '#333333',
    marginBottom: 15,
  },
  button: {
    color: '#111',
    marginBottom: 15,
    borderWidth: 1,
    borderColor: 'blue',
    padding: 10,
    borderRadius: 20,
    fontWeight: '600'
  },
  buttons: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'flex-start'
  }
})

Lastly, there is the register statement:

AppRegistry.registerComponent('timer', () => TimerWrapper)

Now, we can install and import the Audio Player into the Xcode project following the steps in the previous section. Don’t forget to include the sound file as well. When you’re done, navigate to the ch9/timer folder and start the local server with $ react-native start . You should see:

React packager ready.

Go to your Simulator and refresh it. You should see buttons with seconds on them and the switch in the off position. Turn it on to use minutes and the buttons will change. Pressing on 5 minutes will start the countdown showing seconds left, as shown in Figure 9.

I dare you to redesign this little app (make it prettier!), publish it to the App Store, and send me the link. Maybe you can get to the top charts. Flappy Bird did.

Project:Weather App

The idea of this project is to fetch weather forecasts from the OpenWeatherMap API based on the city name provided by the user (Figure 10). In this project we’ll be utilizing Navigator to switch between the screens and show a navigation menu on top with a button to go back.

Also, there will be a “remember me” feature to save the entered city name for future uses. The persistence will be implemented with AsyncStorage .

The resulting forecast data will be shown in a grid with the date, description, and temperature in F and C, as shown in Figure 11.

To get started, use the scaffolding provided by the React Native CLI tool (if you don’t have v0.1.7, follow the instructions at the beginning of this chapter to get it):

$ react-native init weather

The command will output something like this:

This will walk you through creating a new React Native project in /Users/azat/Documents/Code/react/ch9/weather
Installing react-native package from npm...
Setting up new React Native app in /Users/azat/Documents/Code/react/ch9/weather
To run your app on iOS:
   Open /Users/azat/Documents/Code/react/ch9/weather/ios/weather.xcodeproj in Xcode
   Hit the Run button
To run your app on Android:
   Have an Android emulator running (quickest way to get started), or a device connected
   cd /Users/azat/Documents/Code/react/ch9/weather
   react-native run-android

Open the iOS project in Xcode with this command:

$ open ios/weather.xcodeproj

In addition to the already existing index.ios.js , create four files, forecast.ios.js , search.ios.js , weather-api.js , and response.json , so the project structure looks like this:

/weather
  /android
    ...
  /ios
    /weather
      /Base.Iproj
        ...
      /Images.xcassets
        ...
      - AppDelegate.h
      - AppDelegate.m
      - Info.plist
      - main.m
    /weather.xcodeproj
      /project.xcworkspace
        ...
      /xcshareddata
        ...
      /xcuserdata
        ...
      - project.pbxproj
    /weatherTests
      - Info.plist
      - weatherTests.m
  /node_modules
    ...
  - .flowconfig
  - .gitignore
  - .watchmanconfig
  - forecast.ios.js
  - index.android.js
  - index.ios.js
  - package.json
  - response.json
  - search.ios.js
  - weather-api.json

The files search.ios.js and forecast.ios.js will be the components for the first screen, which will have the input field for the city name, and the second screen, which will show the forecast, respectively. But before we start implementing Search and Forecast , let’s code the App component and the navigation that will enable us to switch between the Search and Forecast screens.

In the index.ios.js file, add the React Native classes shown in the following listing. The only classes that should be unfamiliar to you by now are AsyncStorage and PixelRatio —everything else was covered earlier in this chapter:

'use strict'

var React = require('react-native')

var {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  Navigator,
  ListView,
  AsyncStorage,
  TouchableOpacity,
  PixelRatio
} = React

Import Search . The const is an ES6 thing. You can use var or learn about const and let in ES6/ES2016 cheatsheet.

const Search = require('./search.ios.js')

Now let’s create an abstraction for the storage, i.e., AsyncStorage . You can use AsyncStorage directly, but it’s better to have an abstraction like the one shown here. The AsyncStorage interface is very straightforward. It uses the getItem() , removeItem() , and setItem() metody. I’m sure you can guess what they mean. The only interesting part is that for getItem() we need to utilize Promise . The idea behind it is that getItem() results are asynchronous. There’s more on ES6 promises in the cheatsheet.

const storage = {
  getFromStorage(name, callback) {
    AsyncStorage.getItem(name).then((value) => {
      console.log(`AsyncStorage GET for ${name}: "${value}"`)
      if (value) callback(value)
      else callback(null)
    }).done()
  },
  setInStorage(name, value) {
    console.log(`AsyncStorage SET for ${name}: "${value}"`)
    AsyncStorage.setItem(name, value)
  },
  removeItem: AsyncStorage.removeItem
}

Remove the boilerplate component and replace it with App :

const App = React.createClass({
  render() {
    return (

The App component needs to render Navigator . We provide the Search component as the initial route:

      <Navigator
        initialRoute={{
          name: 'Search',
          index: 0,
          component: Search,
          passProps: {
            storage: storage
          }
        }}

The ref property is how we can access the Navigator instance in the App component itself. The navigator object will be in this.refs.navigator , assuming this refers to App :

        ref='navigator'

The navigation bar is the menu at the top of the screen, and we render it by using the Navigator.NavigationBar component and supplying the routeMapper property (we still need to implement this):

        navigationBar={
          <Navigator.NavigationBar
            routeMapper={NavigationBarRouteMapper}
            style={styles.navBar}
          />
        }

While the navigation bar is a nice-to-have but not necessary feature, the next property is important.
It basically renders every route. In this example, I assume that the route argument has everything I need, such as components and properties. Another way to implement Navigator is to pass only IDs in route and resolve the component object from the ID by using some hash table (i.e., a route stack object).

        renderScene={(route, navigator) => {
          let props = route.passProps

You can control where the navigator object is in children by setting it to whatever property you want to use. I keep it consistent; the navigator object is placed under this.props.navigator :

          props.navigator = navigator
          props.name = route.name

After we’ve added navigator and name , the props object is ready for rendering:

          return React.createElement(route.component, props)

And then, let’s close all the parentheses and tags:

        }}
      />
    )
  }
})

We are done with most of the heavy lifting. If you opted not to implement the navigation bar, you can skip NavigationBarRouteMapper . If you want to use the bar, this is how you can implement it.

The route mapper must have certain methods:LeftButton , RightButton , and Title . This pattern was inspired by the official React navigation bar example. The first method checks whether this is the initial route or not with the index == 0 stav. Alternatively, we can check for the name of the scene, such as name == 'Search' .

var NavigationBarRouteMapper = {
  LeftButton(route, navigator, index, navState) {
    if (index == 0) return null

If we pass the first statement, we are on the Forecast. Set the previous route (Search):

    var previousRoute = navState.routeStack[index - 1]

Now, return the button, which is a TouchableOpacity component with Text v něm. I use angle brackets with the previous route’s name as the button label, as shown in Figure 12. You can use Next or something else. This Navigator component is highly customizable. Most likely, you’d have some nicely designed images as well.

    return (
      <TouchableOpacity

The event handler uses the pop() metoda. Similar to Array.pop() , it removes the last element from a stack/array. The last element is the current screen, so we revert back to the previous route:

        onPress={() => navigator.pop()}
        style={styles.navBarLeftButton}>
        <Text style={[styles.navBarText, styles.navBarButtonText ]}>
          {'<'} {previousRoute.name}
        </Text>
      </TouchableOpacity>
    )
  },

We don’t need the right button in this project, but if you need it, you can implement it analogously to the left button. You might want to use a list of routes, such that you know which one is the next one based on the index of the current route.

  RightButton(route, navigator, index, navState) {
    return (
      <View/>
    )
  },

The last method is straightforward. We render the name of the route as the title. You can use the title property instead of name if you wish; just don’t forget to update it everywhere (that is, in initialRoute , renderScene , and push() in Search ).

  Title(route, navigator, index, navState) {
    return (
      <Text style={[styles.navBarText, styles.navBarTitleText]}>
        {route.name}
      </Text>
    )
  }
}

Lastly, the styles! They are easy to read. One new addition is PixelRatio . It will give us the ratio of pixels so we can control the values on a lower level:

var styles = StyleSheet.create({
  navBar: {
    backgroundColor: 'white',
    borderBottomWidth: 1 / PixelRatio.get(),
    borderBottomColor: '#CDCDCD'
  },
  navBarText: {
    fontSize: 16,
    marginVertical: 10,
  },
  navBarTitleText: {
    color: 'blue',
    fontWeight: '500',
    marginVertical: 9,
  },
  navBarLeftButton: {
    paddingLeft: 10,
  },
  navBarRightButton: {
    paddingRight: 10,
  },
  navBarButtonText: {
    color: 'black'
  }
})

Change the weather component to App in the register call:

AppRegistry.registerComponent('weather', () => App)

We are done with one file, and we have two more to go. Moving in the logical sequence of the app flow, we continue with search.ios.js by importing the objects:

'use strict'

var React = require('react-native')
const Forecast = require('./forecast.ios')

var {
  StyleSheet,
  Text,
  TextInput,
  View,
  Switch,
  TouchableHighlight,
  ListView,
  Alert
} = React

Next, we want to declare the OpenWeatherMap API key, which you can get from their website after registering as a developer. Pick the free plan unless you’re sure your app will hit the limits when it becomes number one on iTunes (or is it the App Store?). Refrain from using my keys, and get your own:

const openWeatherAppId = '2de143494c0b295cca9337e1e96b00e0', 
  // This is Azat's key. Get your own!

In the event that OpenWeatherMap changes the response format or if you want to develop offline (as I do), keep the real URL commented and use the local version (weather-api.js Node.js server):

  // openWeatherUrl = 'http://api.openweathermap.org/data/2.5/forecast' // Real API
  openWeatherUrl = 'http://localhost:3000/' // Mock API, start with $ node weather-api

Because this file is imported by index.ios.js , we need to export the needed component. You can create another variable/object, but I just assign the component to module.exports for eloquence:

module.exports = React.createClass({
  getInitialState() {

When we get the initial state, we want to check if the city name was saved. If it was, then we’ll use that name and set isRemember to true , because the city name was remembered in the previous use:

    this.props.storage.getFromStorage('cityName', (cityName) => {
      if (cityName) this.setState({cityName: cityName, isRemember: true})
    })

While we wait for the asynchronous callback with the city name to be executed by the storage API, we set the value to none:

    return ({isRemember: false, cityName: ''})
  },

Next, we handle the switch by setting the state of isRemember , because it’s a controlled component:

  toggleRemember() {
    console.log('toggle: ', this.state.isRemember)
    this.setState({ isRemember: !this.state.isRemember}, ()=>{

If you remember from previous chapters (I know, it was so long ago!), setState() is actually asynchronous. We want to remove the city name if the Remember? toggle is off, so we need to implement removeItem() in the callback of setState() , and not just on the next line (we might have a race condition and the state will be old if we don’t use a callback):

      if (!this.state.isRemember) this.props.storage.removeItem('cityName')
    })
  },

On every change of the city name TextInput , we update the state. This is the handler for onChangeText , so we get the value as an argument, not the event:

  handleCityName(cityName) {
    this.setState({ cityName: cityName})
  },

The search() method is triggered by the Search button and the virtual keyboard’s “enter.” First, we define the states as local variables to eliminate unnecessary typing:

  search(event) {
    let cityName = this.state.cityName,
      isRemember = this.state.isRemember

It’s good to check that the city name is not empty. There’s a cross-platform component Alert for that:

    if (!cityName) return Alert.alert('No City Name',
      'Please enter city name',
      [{text: 'OK', onPress: () => console.log('OK Pressed!')}]
    )

The most interesting piece of logic in the entire app is how we make the external call. Odpověď je snadná. We’ll use the new fetch API, which is already part of Chrome. We don’t care about Chrome right now too much; all we need to know is that React Native supports it. In this example, I resorted to the ES6 string interpolation (a.k.a. string template) to construct the URL. If you’re using the local server, the response will be the same (response.json ), so the URL doesn’t matter.

    fetch(`${openWeatherUrl}/?appid=${openWeatherAppId}&q=${cityName}&units=metric`, {
      method: 'GET'
    }).then((response) => response.json())
      .then((response) => {

Once we get the data, we want to store the city name. Maybe you want to do it before making the fetch volání. It’s up to you.

        if (isRemember) this.props.storage.setInStorage('cityName', cityName)

The ListView will render the grid, but it needs a special object data source. Create it like this:

        let dataSource = new ListView.DataSource({
          rowHasChanged: (row1, row2) => row1 !== row2
        })

Everything is ready to render the forecast. Use the Navigator object by invoking push() and passing all the necessary properties:

        this.props.navigator.push({
          name: 'Forecast',
          component: Forecast,

passProps is an arbitrary name. I followed the NavigatorIOS syntax here. You can pick another name. For the ListView , we populate the rows from the JavaScript/Node array with cloneWithRows() :

          passProps: {
            forecastData: dataSource.cloneWithRows(response.list),
            forecastRaw: response
          }
        })
      })
      .catch((error) => {
        console.warn(error)
      })
  },

We are done with the methods of Search . Now we can render the elements:

  render: function() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>
          Welcome to Weather App, React Quickly project
        </Text>
        <Text style={styles.instructions}>
          Enter your city name:
        </Text>

The next element is a TextInput for the city name. It has two callbacks, onChangeText , which triggers handleCityName , and onEndEditing , which calls search :

        <TextInput
          placeholder="San Francisco"
          value={this.state.cityName}
          returnKeyType="search"
          enablesReturnKeyAutomatically={true}
          onChangeText={this.handleCityName}
          onEndEditing={this.search} style={styles.textInput}/>

The last few elements are the label for the switch, the switch itself, and the Search button:

        <Text>Remember?</Text>
        <Switch onValueChange={this.toggleRemember} value={this.state.isRemember}></Switch>
        <TouchableHighlight onPress={this.search}>
          <Text style={styles.button}>Search</Text>
        </TouchableHighlight>
      </View>
    )
  }
})

And of course the styles—without them, the layout and fonts will be all skewed. The properties are self-explanatory for the most part, so we won’t go into detail on them.

var styles = StyleSheet.create({
  navigatorContainer: {
    flex: 1
  },
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
  textInput: {
    borderColor: '#8E8E93',
    borderWidth: 0.5,
    backgroundColor: '#fff',
    height: 40,
    marginLeft: 60,
    marginRight: 60,
    padding: 8,
  },
  button: {
    color: '#111',
    marginBottom: 15,
    borderWidth: 1,
    borderColor: 'blue',
    padding: 10,
    borderRadius: 20,
    fontWeight: '600',
    marginTop: 30
  }
})

So, we invoke the push() method from the Search component when we press Search. This will trigger an event in the Navigator element:namely renderScene , which renders the forecast. Let’s implement it. I promise, we are almost done!

The forecast.ios.js file starts with importations. By now, if this is unfamiliar to you, I am powerless.

'use strict'

var React = require('react-native')
var {
  StyleSheet,
  Text,
  TextInput,
  View,
  ListView,
  ScrollView
} = React

I wrote this function, mostly for Americans, to calculate F from C . It’s probably not very precise, but it’ll do for now:

const fToC = (f) => {
  return Math.round((f - 31.996)*100/1.8)/100
}

The ForecastRow component is stateless (more on stateless components in chapter 10). Its sole purpose is to render a single forecast item:

const ForecastRow = (forecast)=> {
  return (
    <View style={styles.row}>
      <View style={styles.rightContainer}>
        <Text style={styles.subtitle}></Text>
        <Text style={styles.subtitle}>

In the row, we output the date (dt_txt ), description (rainy or sunny), and temperatures in C and F (figure 9-X). The latter is achieved by invoking the fToC function defined earlier in this file:

          {forecast.dt_txt}: {forecast.weather[0].description}, {forecast.main.temp}C/{fToC(forecast.main.temp)}F
        </Text>
       </View>
    </View>
  )
}

The result will look as shown in figure 9-X.

Next, we export the Forecast component, which is a ScrollView with Text and a ListView :

module.exports = React.createClass({
  render: function() {
    return (
      <ScrollView style={styles.scroll}>
        <Text style={styles.text}>{this.props.forecastRaw.city.name}</Text>

The ListView takes dataSource and renderRow properties to render the grid. The data source must be of a special type. It cannot be a plain JavaScript/Node array:

        <ListView dataSource={this.props.forecastData} renderRow={ForecastRow} style={styles.listView}/>
      </ScrollView>
    )
  }
})

And the styles. Tadaah!

var styles = StyleSheet.create({
  listView: {
    marginTop: 10,
  },
  row: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#5AC8FA',
    paddingRight: 10,
    paddingLeft: 10,
    marginTop: 1
  },
  rightContainer: {
    flex: 1
  },
  scroll: {
    flex: 1,
    padding: 5
  },
  text: {
    marginTop: 80,
    fontSize: 40
  },
  subtitle: {
    fontSize: 16,
    fontWeight: 'normal',
    color: '#fff'
  }
})

The last final touch is if you’re working offline and using a local URL. There are two files you need to have:

  1. response.json—Response to the real API call for London
  2. weather-api.js—Ultra-minimalistic Node web server that takes response.json and serves it to a client

Go ahead and copy response.json from GitHub. Then implement this Node.js server using only the core modules (I love Express or Swagger, but using them here is an overkill):

var http = require('http'),
  forecastData = require('./response.json')

http.createServer(function(request, response){
  response.end(JSON.stringify(forecastData))
}).listen(3000)

Start the server with $ node weather-api , bundle the React Native code with $ react-native start , and reload the Simulator. The bundler and the server must be running together, so you might need to open a new tab or a window in your terminal app/iTerm.

Note:if you get an “Invariant Violation:Callback with id 1–5” error, make sure you don’t have the Chrome debugger opened more than once.

You should see an empty city name field. That’s okay, because this is the first time you’ve launched the app. I intentionally left the logs in the storage implementation. You should see the following when you open DevTools in the Chrome tab for debugging React Native (it typically opens automatically once you enable it by going to Hardware->Shake Gestures->Debug in Chrome—not that you are going to shake your laptop!):

AsyncStorage GET for cityName: "null"

Play with the toggle, enter a name (Figure 13), and get the weather report. The app is done. Výložník! Now put some nice UI on it and ship it!

Quiz

  1. How do you create a new React Native project:create files manually, or run $ npm init , $ react-native init , or $ react native init ?
  2. What type of data does a ListView take:array, object, or data source? (Data source)
  3. One of the benefits of React Native vs. native development is that React Native has the live reload ability. Pravda nebo lež? (True)
  4. You can use any CSS in the styles of the React Native StyleSheet objekt. Pravda nebo lež? (False)
  5. Which Objective C file can you switch the React Native bundle location in:bundle.cc, AppDelegate.m, AppDelegate.h, package.json, or index.ios.js? (AppDelegate.m)

Actions

Learning just by reading is not as effective as learning by reading and then doing. Ano. Even a good book like this. So take action NOW to solidify the knowledge.

  • Watch React Native Quickly screencasts at Node.Unversity which will walk you through the Weather app
  • Run Weather and Timer on your computer from the source code
  • Change text such as button labels or menu names, see results in the Simulator
  • Change a sound file in Timer
  • Add geolocation to Weather (see Geolocation)

Summary

This was a been a quick book, but we covered not not just one but two projects. In addition to that, we’ve also covered:

  • How React Native is glued to the Objective C code in Xcode projects
  • Main components, such as View , Text , TextInput , Touchable s, and ScrollView
  • Implementing an app with Navigator
  • How to persist the data locally on the device
  • Using the fetch API to communicate with an external HTTP REST API server (you can use the same method to persist the data on the external server, or do a login or logout)

React Native is an amazing piece of technology. I was really surprised, in a positive way, once I started learning and using it. There’s a lot of evidence that React Native may become the next de facto way of developing mobile apps. The live reload feature can enable developers to push code to their apps without resubmitting them to the App Store—cool, right?

Quiz Answers

  1. $ react-native init because manual file creation is tedious and prone to errors
  2. Data source
  3. True
  4. False
  5. AppDelegate.m

No