Эта книга представляет собой руководство по началу работы с React Native для разработки мобильных iOS. Вы можете найти исходный код и рукопись в https://github.com/azat-co/react-native-quickly. Вы можете прочитать эту книгу онлайн здесь или на reactnativequickly.com, или если вы предпочитаете видео, вы можете посмотреть видео проекта на Node.University: http://node.university/courses/ реагируй-родной-быстро .

В этой книге я познакомлю вас с React Native для разработки нативных мобильных устройств под iOS и Android… и сделаю это быстро. Мы рассмотрим такие темы, как

  • Почему React Native великолепен
  • Настройка React Native Development для iOS
  • Привет, мир и интерфейс командной строки React Native
  • Стили и Flexbox
  • Основные компоненты пользовательского интерфейса React Native
  • Импорт модулей в проект Xcode
  • Проект: Таймер
  • Проект: приложение "Погода"

Эта книга о быстром начале работы с React, а не о React Native, которая технически представляет собой отдельную библиотеку (или некоторые могут даже назвать ее фреймворком). Но я решил, что после восьми глав работы с React для веб-разработки было бы интересно применить наши знания в мобильной разработке, используя эту потрясающую библиотеку. Вы будете удивлены, сколько навыков React Native вы уже знаете по React.

Всегда существует баланс между слишком сложными или слишком простыми примерами, а значит, нереалистичными и бесполезными. В этой книге приготовьтесь создать два мобильных приложения: приложения Timer и Weather. В приложении Погода есть 3 скринкаста, которые вы можете посмотреть на Node.Unversity. Они проведут вас через приложение Погода.

[Примечание]

Читать сообщения в блогах - это хорошо, но смотреть видеокурсы еще лучше, потому что они более увлекательны.

Многие разработчики жаловались на нехватку доступного качественного видеоматериала на Node. Смотреть видео на YouTube отвлекает, а платить 500 долларов за видеокурс по Node - это безумие!

Посетите Node University, где есть БЕСПЛАТНЫЕ видео-курсы на Node: node.university.

[Конец примечания]

Исходный код проектов (а также рукопись для отправки проблем / ошибок) находится в репозитории https://github.com/azat-co/react-native-quickly. Наслаждаться!

Почему React Native великолепен

Приложения React Native - это не то же самое, что гибридные или так называемые приложения HTML5. Если вы не знакомы с гибридным подходом, это когда веб-сайт обернут в браузер без заголовка. Безголовый браузер - это представление браузера без строки URL или кнопок навигации. По сути, разработчики создают адаптивные веб-сайты, используя обычные веб-технологии, такие как JavaScript, HTML и CSS, и, возможно, такие фреймворки, как jQuery Mobile, Ionic, Ember или Backbone. Затем они упаковывают его как собственное приложение вместе с этим автономным браузером. В конце концов, вы можете повторно использовать одну и ту же базу кода на разных платформах, но опыта использования гибридных приложений часто не хватает. Обычно они не такие быстрые или не имеют определенных функций по сравнению с собственными приложениями. Среди самых популярных фреймворков для гибридных приложений - Sencha Touch, Apache Cordova, PhoneGap и Ionic.

С другой стороны, приложение React Native - это не веб-сайт, завернутый в безголовый браузер. Это собственный код Objective C или Java, который взаимодействует с JavaScript React. Это дает следующие преимущества по сравнению с собственной разработкой:

Примечание: если вам понравился этот пост и вы заинтересованы в корпоративном тренинге по JavaScript, Node.js и React.js на сайте для повышения производительности вашей команды, тогда свяжитесь с NodeProgram.com.

  • Горячая / живая перезагрузка. Разработчики могут перезагружать свои приложения без перекомпиляции, что ускоряет разработку и устраняет необходимость в сложных редакторах WYSIWYG и IDE.
  • Система верстки Flexbox. Это синтезированная система макетов, аналогичная CSS и допускающая кроссплатформенную разработку.
  • Отладка Chrome. Разработчики могут использовать уже знакомые DevTools.
  • Напишите один раз и заставьте его работать на разных платформах.
  • Перенос из Интернета. Легко реагировать, например, с такими фреймворками, как ComponentKit.
  • Воспользуйтесь огромным количеством инструментов с открытым исходным кодом, утилит, библиотек, знаний, передового опыта, ES6 / 7 + и книг по JavaScript (самому популярному языку программирования в мире).
  • Используйте собственные элементы, которые лучше и мощнее веб-технологий (подход HTML5 / обертка).
  • Реагировать. Никакой конкретной привязки данных, управления событиями или микроуправления представлениями - все это имеет тенденцию к увеличению сложности. React использует декларативный подход и простой масштабируемый однонаправленный поток данных.

По этим причинам неудивительно, что как крупные, так и мелкие компании вскакивают на поезд React Native и отказываются как от гибридных, так и от нативных подходов. Каждый день я читаю сообщения в блогах, в которых говорится, что такая-то компания или какой-то разработчик iOS перешли на React Native, и что они довольны этим переходом. Готовы ли вы приступить к разработке мобильного приложения следующего поколения?

Настройка React Native Development

В этой главе рассматривается только разработка на React Native для iOS. Я буду использовать только универсальные кроссплатформенные компоненты, например Navigator, а не NavigatorIOS, поэтому предоставленный код должен работать и для Android. Однако я не буду вдаваться в подробности того, как вы будете компилировать проекты Android.

Если вы не работаете на оборудовании Apple с Mac OS X, вы можете установить виртуальную машину под управлением Mac OS X в ОС Linux или Windows, следуя этому руководству. Двигаясь вперед, я предполагаю, что мы все работаем над Mac OS X, виртуальным или нет, для создания приложений для iOS.

Чтобы все было установлено, вы можете сделать это вручную или воспользоваться менеджером пакетов. Поскольку мы работаем в среде Mac OS X, я рекомендую использовать Homebrew (a.k.a. brew) для установки некоторых необходимых инструментов. Если у вас еще нет Homebrew, вы можете перейти на его веб-сайт http://brew.sh или запустить эту команду Ruby (Mac OS X поставляется с Ruby):

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

Нам потребуются следующие инструменты и библиотеки:

  • Node.js v5.1 и npm v3.3.12 - Если вы читали главы 1–8, они у вас уже должны быть. Если вы прыгнули прямо здесь, следуйте инструкциям в приложении Б.
  • Watchman v4.1.0 - этот инструмент будет отслеживать и обновлять в соответствии с изменениями файла исходного кода. Используйте $ brew install [email protected], чтобы установить его.
  • Google Chrome - браузер позволит вам отлаживать приложения React Native во время разработки. Вот ссылка для скачивания.
  • React Native CLI v0.1.7 - этот инструмент позволит вам создавать шаблоны для ваших приложений React Native. Установите его с помощью $ npm install -g [email protected].
  • Xcode v7.2 - IDE, компиляторы и SDK для iOS, OS X, tvOS и watchOS. Чтобы установить его, щелкните ссылку https://developer.apple.com/xcode/download, чтобы открыть Mac App Store.
  • Flow - средство проверки статического типа для JavaScript. Чтобы установить его с помощью Homebrew, запустите $ brew install [email protected].

Я рекомендую использовать NVM v0.29.0, n или аналогичный менеджер версий Node. Этот шаг не является обязательным, но рекомендуется, поскольку он означает, что вы можете перейти на Node.js v5.1, даже если ваша основная версия более поздняя. Чтобы использовать Homebrew, выполните $ brew install nvm и следуйте инструкциям.

Ваша система должна быть готова к разработке приложений для iOS. Начнем с типичного примера программирования - Hello World.

Привет, мир и интерфейс командной строки React Native

Сначала перейдите в папку, в которой вы хотите разместить свой проект. Мой - / Users / azat / Documents / Code / react / ch9 /. Затем запустите команду терминала $ response-native init, чтобы запустить проект, создав проекты iOS и Android, package.json и другие файлы и папки:

$ react-native init hello

Ждать. Это может занять некоторое время. В данный момент происходит несколько вещей. Очевидно, папка hello создана. Затем инструмент создает package.json. (Мне нравится, что в настоящее время Node и npm повсюду. В 2012 году этого не было!) В package.json глобальный интерфейс командной строки для реагирования, который является глобальным, устанавливает локальную зависимость, ориентированную на реагирование. Это похоже на запуск $ npm i response-native - save.

После этого шага глобальный CLI, поддерживающий реакцию, запускает локальный код из файла hello / node_modules / response-native / local-cli / cli.js, а тот, в свою очередь, запускает вспомогательный сценарий bash hello / node_modules / response-native / init.sh. Этот сценарий bash создает каркас с кодом React Native в файлах index.ios.js и index.android.js, а также в проектах iOS и Android в папках ios и android.

В папке ios инструмент создает файлы проекта Xcode с кодом Objective C. Это наша цель прямо сейчас. Вот шаблонная структура папок, созданная инструментом:

/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

Как только все будет создано, вы вернетесь в командную строку. Вывод на моем компьютере был таким, который даже говорит мне, как запускать приложения:

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

У вас есть два варианта. Вы можете вручную открыть Xcode и выбрать «Открыть» (Command + O) в меню «Файл», открыть файл hello.xcodeproj и щелкнуть черный прямоугольник для построения и запуска. Или вы можете перейти в папку с помощью $ cd hello, запустить $ open ios / hello.xcodeproj и нажать «play» в Xcode для сборки и запуска.

Если вы выполнили все шаги правильно, вы увидите новое окно терминала с надписью React Packager. Он начинается с сообщения:

~/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)

Так что же здесь происходит? React Native упаковывает наши файлы JavaScript React Native и обслуживает их на localhost: 8081. Верно, это как любой другой веб-сервер, если вы откроете свой браузер по адресу http: // localhost: 8081 / index.ios.bundle? Platform = ios & dev = true. Откройте его сейчас в своем браузере. Ищите привет. Вы увидите, что код React Native собран в один большой файл. Это должно звучать знакомо большинству веб-разработчиков. ;-)

Где я взял URL-адрес http: // localhost: 8081 / index.ios.bundle? Platform = ios & dev = true? Он находится в файле hello / ios / hello / AppDelegate.m в строке 34 (вы используете ту же версию, что и я, верно?):

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

Код Objective C захватывает JavaScript с сервера. Это вариант номер один по умолчанию. Есть второй вариант, который сейчас комментируется. Он берет код JavaScript из статического файла (строка 42 того же файла). Приятно иметь варианты!

Комментарии говорят нам, как мы можем раскрутить сервер. Это просто команда $ npm start, которая запускает $ response-native start, так что мы можем использовать и последнюю. Поэтому, если вы переключаетесь между проектами или не хотите использовать процесс терминала, автоматически открываемый Xcode, вы всегда можете запустить новый сервер. Просто имейте в виду, как и в случае с любым другим сервером, что у вас не может быть двух из них, прослушивающих один и тот же порт. Поэтому завершите старый процесс, прежде чем запускать новый сервер на localhost: 8081.

Для запуска окна симулятора требуется некоторое время. Я предпочитаю работать с iPhone 6, а не с iPhone 6 Plus. Таким образом у меня появляется больше места для развития на моем экране. К этому моменту у вас должно быть открыто окно симулятора. Слоняться поблизости. Как показано на рисунке 1, здесь особо не на что смотреть.

Идите вперед и откройте файл index.io.js. Вы можете увидеть знакомый код JavaScript / Node. Если вас еще не устраивает ES6 (или ES2015 - его официальное название), взгляните на главу 10 и приложение I.

В начале файла есть оператор деструктуризации для импорта объектов из React Native:

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

Затем вы можете увидеть своего старого доброго друга React.createClass () с методом рендеринга:

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> ); } });

Черт возьми, с такими хорошими комментариями я скоро выйду из бизнеса, а это значит, что мне не нужно будет писать книги. ;-) Как говорится, нажатие Command + R в Симуляторе перезагрузит его. Идите вперед и измените «Добро пожаловать в React Native!» в "Hello World!" Сохраните index.ios.js и перезагрузите приложение в окне Simulator.

Примечание: если вы используете нестандартную раскладку клавиатуры, такую ​​как Dvorak или Colemak (как и я), в окне симулятора вам придется использовать стандартную раскладку США для горячих клавиш, а также для набора текста.

Обратите внимание на изменения и обратите внимание, что нам не пришлось перестраивать проект Xcode. Watchman обновил пакет после того, как мы сохранили файл. Новый код обслуживается на сервере по адресу localhost: 8081. Вы можете увидеть текст Hello World! в браузере, если вы перейдете на http: // localhost: 8081 / index.ios.bundle? platform = ios & dev = true. Как только мы перезагрузили симулятор, новый код был там!

В index.ios.js есть еще две интересные вещи (а затем мы перейдем к изучению каждого компонента по отдельности): StyleSheet и AppRegistry. Их нет в Web React, поэтому позвольте мне их объяснить.

Стили и Flexbox

Первый - это способ создания макета, стилей и форматирования элементов. Создаем объект с помощью StyleSheet.create (). Например, это наши стили 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, }, });

Надеюсь, вы догадались, что означают некоторые свойства, например backgroundColor и fontSize. Они похожи на background-color и font-size в CSS, и вы уже знаете, что React использует camelCase вместо тире. Другие свойства стиля, такие как flex, специфичны для React Native. Вот что они означают:

  • сгибать
  • justifyContent
  • alignItems
  • flexDirection

Числа в свойствах стиля - это точки, а не пиксели. Разница в том, что точки могут означать 1 или более пикселей в зависимости от экрана, поэтому использование точек освобождает разработчиков от написания условий if / else для различных форматов экрана. В частности, на старых iPhone, таких как iPhone 3GS, 1 балл равен 1 пикселю (1: 1). С другой стороны, на новых iPhone с экранами Retina, таких как iPhone 6, 1 точка представляет собой квадрат размером 2 × 2 пикселей (1: 2).

Последний оператор файла похож на ReactDOM.render () из разработки Web React:

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

Привет, он регистрирует наш компонент в реестре. Вы можете изменить имя в функции жирной стрелки (второй аргумент) на любое другое, но воздержитесь от изменения первого аргумента. Жирные стрелки ES6 рассматриваются в главе 10 и приложении I. А сейчас давайте рассмотрим компоненты React Native более подробно.

Основные компоненты пользовательского интерфейса React Native

Вы могли заметить, что в методе рендеринга мы используем специальные теги / элементы, такие как ‹View› и ‹Text› вместо ‹div› или ‹p›. Эти специальные элементы или компоненты React Native берутся из библиотеки react-native. Их там целая куча, и я уверен, что скоро их будет больше. Есть компоненты, специфичные для iOS и Android, а также синтетические компоненты, которые будут работать на разных платформах. Обычно компоненты, предназначенные только для iOS, имеют в конце названия IOS (например, NavigatorIOS), а универсальные кроссплатформенные компоненты не имеют таких окончаний (например, Navigator).

Для описания всех компонентов React Native потребуется отдельная книга. Кроме того, как я уже говорил, разработчики сообщества и Facebook постоянно и без устали добавляют новые компоненты и обновляют существующие. Для получения полного актуального списка поддерживаемых компонентов лучше обратиться к официальной документации. Однако, чтобы иметь возможность разрабатывать минимальные мобильные приложения с помощью React Native, вам необходимо изучить основные (на мой взгляд) компоненты. Они есть:

  • Вид - компонент основного вида. У каждого рендера должен быть хотя бы пустой вид.
  • Текст - текстовый компонент. Весь текст должен быть заключен в этот компонент, в отличие от текста в Web React.
  • TextInput - компонент поля ввода формы. Используйте его для захвата пользовательского ввода.
  • ScrollView - просмотр с прокручиваемым содержимым. Используйте его, когда ваш контент не умещается на одном экране.
  • ListView - просмотр со структурированными данными. Используйте его для вывода списков или таблиц.
  • TouchableHighlight - Пользовательский сенсорный компонент. Используйте его для захвата событий касания пользователя, аналогично тегам привязки в веб-разработке.
  • Переключатель - логический переключатель включения / выключения. Используйте его для настроек и форм.
  • Навигатор - настраиваемый компонент навигации. Используйте его для перехода между экранами и реализации панели навигации и / или панели навигации хлебных крошек.

Все эти компоненты были выбраны, потому что знание их даст вам минимум для создания полезных приложений, как вы увидите в проектах Timer и Weather App. Также эти компоненты универсальны; то есть вы можете (и должны) использовать их для iOS и Android. Возможно, вы даже можете использовать одну и ту же базу кода для index.ios.js и index.android.js.

В этом разделе книги я буду использовать фрагменты кода из проектов Timer и Weather App, чтобы сделать примеры более реалистичными, чем просто некоторые foo-bar. Код для таймера находится в таймере. Код для приложения Погода - это погода.

Вид

Как я упоминал ранее, View - это самый базовый компонент. Если вы не знаете, что использовать, используйте View. Вы можете заключить несколько других компонентов в представление, аналогично их заключению в ‹div›, потому что render () должен возвращать только один элемент. Например, чтобы вывести количество секунд до конца и метку под ним, оберните их в представление:

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

Текст

Компонент "Текст" предназначен для визуализации текста. Как и большинство других компонентов, мы можем снабдить его стилями. Например, этот элемент Text использует Flex и имеет размер шрифта 36, отступ сверху 40 и поле 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 }, ... })

Результат показан на рисунке 1.

Для удобства мы можем объединить два или более объекта стиля в свойстве стиля, используя массив. Например, этот элемент Text использует стили из navBarText и navBarButtonText:

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

Атрибут стиля и комбинирование стилей не являются эксклюзивными для текста. Вы можете применить их к другим компонентам.

Ввод текста

TextInput - это компонент поля ввода. Обычно вы используете его в формах для захвата вводимых пользователем данных, таких как адрес электронной почты, пароль, имя и т. Д. Этот компонент имеет некоторые знакомые свойства, такие как:

  • заполнитель - пример текста, который будет отображаться, когда значение пусто
  • value - значение поля ввода
  • style - атрибут стиля

Другие атрибуты специфичны для React Native. Основные из них:

  • enableReturnKeyAutomatically - Если false (значение по умолчанию), запрещает пользователю отправлять пустое текстовое значение путем отключения клавиши возврата.
  • onChange - метод, вызываемый при изменении значения. Передает объект события в качестве аргумента.
  • onChangeText - метод, вызываемый при изменении значения. Передает текстовое значение в качестве аргумента.
  • onEndEditing - метод, вызываемый, когда пользователь нажимает клавишу возврата на виртуальной клавиатуре.
  • multiline - если true (по умолчанию false), поле может занимать несколько строк.
  • keyboardType - одно из значений перечислителя, например «по умолчанию», «числовой» или «адрес электронной почты».
  • returnKeyType - Перечислитель для ключа возврата, например 'default', 'go', 'google', 'join', 'next', 'route', 'search', 'send', 'yahoo', 'done', или «экстренный вызов». Только для iOS.

Полный список актуальных свойств TextInput для iOS и Android находится по адресу https://facebook.github.io/react-native/docs/textinput.html#props.

Рассмотрим этот пример, который отображает поле ввода названия города с помощью обработчика this.search. Кнопка на клавиатуре скажет Search, значение будет присвоено состоянию (управляемый компонент!), А заполнитель будет Сан-Франциско:

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

Результат показан на рисунке 2, где вы можете увидеть клавишу поиска на виртуальной клавиатуре.

С помощью свойства onChangeText мы получаем значение поля ввода в качестве аргумента функции-обработчика (handleCityName (event)). Например, чтобы обработать название города и установить состояние cityName в управляемом компоненте, нам нужно реализовать handleCityName следующим образом:

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

С другой стороны, если вам нужно больше, чем текст, есть onChange. Когда событие поступает в функцию-обработчик onChange, аргумент события имеет свойство с именем nativeEvent, а это свойство, в свою очередь, имеет свойство с именем text. Вы можете реализовать обработчик onChange следующим образом:

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

ScrollView

Это расширенная версия компонента View. Это позволяет прокручивать контент, поэтому вы можете прокручивать вверх и вниз с помощью сенсорных жестов. Это полезно, когда контент не умещается на одном экране. Например, я могу использовать ScrollView в качестве корня моего render (), потому что я знаю, что timerOptions может быть очень большим массивом, таким образом отображая много строк данных (компонентов Button):

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> ) } })

Посмотреть список

ListView - это представление, которое отображает список строк из предоставленных данных. В большинстве случаев вы хотите обернуть ListView в ScrollView. Данные должны быть в определенном формате. Используйте dataSource = new ListView.DataSource () для создания объекта источника данных, затем используйте dataSource.cloneWithRows (list), чтобы заполнить источник данных данными из стандартного массива JavaScript.

Вот пример. Сначала мы создаем объект источника данных:

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

Затем мы используем метод cloneWithRows для заполнения данных из массива response.list:

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

Пока не обращайте внимания на вызов навигатора. Об этом позже в этой главе.

У нас есть данные, поэтому теперь давайте отрендерим ListView, предоставив свойства dataSource и renderRow. Например, это список информации о прогнозе, где каждая строка представляет собой прогноз на определенный день. Родителем ListView является 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> ) } })

Как вы можете догадаться, renderRow, который в этом примере является ForecastRow, является еще одним компонентом, который отвечает за визуализацию отдельного элемента из предоставленного источника данных. Если нет методов или состояний, вы можете создать компонент без состояния (подробнее о компонентах без состояния в главе 10). В ForecastRow мы выводим дату (dt_txt), описание (description) и температуру (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> ) }

Вы можете добиться функциональности ListView с помощью простой конструкции Array.map (). В этом случае источник данных не нужен.

Осязаемый

TouchableHighlight фиксирует события касания пользователя. Разработчики внедряют кнопки, похожие на теги привязки (‹a›) в веб-разработке. Действие передается как значение свойства onPress. Чтобы реализовать кнопку, нам также нужно поместить внутрь нее текст.

Например, это кнопка, которая запускает startTimer и имеет текст, состоящий из свойства времени и слова «минуты» или «секунды»:

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> ) } })

Сам по себе стиль TouchableHighlight - ничто; по этой причине, когда мы реализуем кнопки, мы либо стилизуем текст внутри TouchableHighlight (рисунок 3), либо используем изображение с компонентом Image.

Компоненты, похожие на TouchableHighlight:

  • TouchableNativeFeedback
  • TouchableOpacity
  • TouchableWithoutFeedback

Выключатель

Вероятно, вы видели и использовали компонент Switch или аналогичный нативный элемент много раз. Наглядный пример показан на рисунке 9-X. Это небольшой переключатель, который не отличается от флажка. Это логический элемент ввода включения / выключения, который пригодится в формах и настройках приложений.

При реализации Switch вы предоставляете как минимум два свойства: onValueChange и value (снова управляемый компонент!). Например, этот переключатель заставляет приложения сохранять название города или нет:

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

В обработчике toggleRemember я установил для состояния значение, противоположное текущему 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') }) }, // ...

Навигатор

Навигатор - это настраиваемый компонент навигации, позволяющий перемещаться между экранами в приложении. Мы можем использовать его для реализации панели навигации и / или панели навигации хлебных крошек. Панель навигации - это меню в верхней части экрана с кнопками и заголовком.

Существует также NavigatorIOS, которая не используется Facebook и поэтому официально не поддерживается и не поддерживается сообществом. NavigatorIOS имеет встроенную панель навигации, но работает только для разработки под iOS. Другой недостаток заключается в том, что NavigatorIOS не обновляет маршруты / экраны при изменении свойств этих маршрутов. И наоборот, Navigator можно использовать на iOS и Android, и он обновляет маршруты при изменении переданных им свойств. Вы можете настроить панели навигации по своему вкусу.

Поскольку Navigator является гибким, я нашел несколько способов его реализовать. Существует метод, в котором у вас есть стек маршрутов, а затем вы перемещаетесь, используя идентификаторы маршрута и методы вперед / назад. Я остановился на этом шаблоне, который использует абстракцию и интерфейс NavigatorIOS (passProps). Допустим, компонент приложения - это тот компонент, который вы регистрируете в AppRegistry. Затем вы хотите отобразить навигатор в методе визуализации приложения:

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) }} /> ) } })

Вы можете наблюдать несколько атрибутов навигатора:

  • initialRoute - самый первый объект маршрута, который мы визуализируем.
  • ref - свойство элемента App, в котором будет находиться объект Navigator. Мы можем использовать его для перехода к новым сценам.
  • navigationBar - верхнее меню с заголовком и кнопками влево и вправо.
  • renderScene - метод, который запускается при событии навигации для каждого маршрута. Мы получаем объект маршрута и визуализируем компонент с помощью route.component и route.passProps.

Чтобы перейти на новый экран, например Forecast (компонент Forecast), и передать ему свойства, вызовите navigator.push ():

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

В этом примере я передаю компонент и свойства с каждым вызовом push (). Если вы используете стек маршрутов, который в основном представляет собой список компонентов, вы можете передать только идентификатор или имя компонента, а не весь объект, и получить объект из стека. Как обычно, есть несколько способов снять шкуру с сома.

Импорт модулей в проект Xcode

Что, если вы хотите использовать компонент React Native от сообщества, то есть что-то, что не является частью react-native, но предоставляется как отдельный модуль npm? Вы можете импортировать модуль в свой проект!

В таймере нам нужно воспроизводить звук, когда время истекло. На момент написания этой статьи (январь 2016 г.) официального компонента для звуков не было, но есть несколько модулей пользовательского пространства. Один из них - react-native-audioplayer. Сначала установите его с помощью npm в папку проекта:

$ npm install [email protected] --save

В данный момент мы ориентируемся на iOS, поэтому установка выполняется следующим образом:

  1. Откройте свой проект в Xcode.
  2. В Xcode найдите Project Navigator на левой боковой панели.
  3. В навигаторе проекта щелкните правой кнопкой мыши "Библиотеки".
  4. В контекстном меню нажмите «Добавить файлы в таймер». (При необходимости замените «таймер» другим названием проекта.)
  5. Перейдите к node_modules / response-native-audioplayer. Добавьте файл RNAudioPlayer.xcodeproj. Результат показан на рисунке 5.

  1. В Project Navigator выберите свой проект (таймер).
  2. Щелкните цель сборки для таймера в списке целей (рисунок 9-X).

  1. Щелкните вкладку Build Phases, чтобы открыть ее.
  2. Разверните «Связать двоичный файл с библиотеками», щелкнув по нему.
  3. Нажмите кнопку «плюс» (+) и добавьте libRNAudioPlayer.a в рабочую область или просто перетащите libRNAudioPlayer.a из навигатора проекта. Он находится в разделе Libraries / RNAudioPlayer.xcodeproj / Products.
  4. Запустите свой проект (нажмите Command + R или щелкните черный прямоугольник, обозначающий «играть»).

Если вы все сделали правильно, в файле index.ios.js вы можете импортировать модуль с помощью require ():

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

И проиграйте звук с помощью play ():

AudioPlayer.play('flute_c_long_01.wav')

Звуковой файл должен быть включен в комплект. Для этого выберите Copy Bundle Resources и добавьте flute_c_long_01.wav или свой собственный звуковой файл, как показано на рисунке 7.

Это все приготовления. Теперь мы можем реализовать Таймер!

Проект: Таймер

Вы видели фрагменты из приложения Timer (рис. 8), которое находится в таймере. Думаю, будет полезно, если мы сразу займемся реализацией. Главный файл - index.ios.js. Он состоит из трех компонентов, в отличие от моего браузера / веб-таймера React Timer от React Quickly (Manning, 2016), (GitHub):

  • TimerWrapper - умный компонент, который имеет большую часть логики для таймера
  • Таймер - тупой компонент, который воспроизводит звук, когда время истекло, и отображает количество оставшихся секунд
  • Кнопка - компонент, который показывает кнопку и запускает обратный отсчет, вызывая обработчик, переданный ему родительским элементом (TimerWrapper).

Мы запускаем файл index.ios.js с импорта React Native, его объектов и аудиоплеера:

'use strict' var React = require('react-native'), AudioPlayer = require('react-native-audioplayer') var { AppRegistry, StyleSheet, Text, View, ScrollView, TouchableOpacity, Switch } = React

Следующий оператор объявляет массив опций для кнопок таймера, которые мы превратим в количество секунд или минут с помощью Switch:

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

Я улучшил TimerWrapper из проекта главы 5 динамической генерацией кнопок и переключением секунд на минуты. Переключатель использует состояние isMinutes, поэтому давайте сначала установим его в false. Напомню, что в этом примере используется некоторый синтаксис ES6 + / ES2015 +. Если вы не знакомы с ним или не уверены, знакомы ли вы с ним, ознакомьтесь с главой 10 и приложением I.

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

Начальное значение isMinutes - false. toggleTime - это обработчик для Switch. Мы меняем значение isMinutes на логическое «нет» (!). Важно установить время равным нулю, иначе звук будет воспроизводиться каждый раз, когда мы нажимаем переключатель. Воспроизведение звука зависит от времени, равного 0, поэтому, если мы установим его на ноль, он не будет воспроизводиться. Звуковая логика находится в компоненте Таймер. Алгоритм React решает повторно отрендерить его, когда мы меняем состояние isMinutes:

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

Следующий метод запускает таймеры. Если вы следили за проектом в главе 5, вы знаете, как он работает. React Native предоставляет API для таймеров, то есть clearInterval () и setInterval () как глобальных объектов. Число в состоянии времени всегда в секундах, даже если мы видим минуты на кнопках и переключатель включен:

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}) },

В методе рендеринга мы используем простой итератор map () для создания столбца кнопок. Он заключен в ScrollView, так что вы действительно можете сойти с ума от массива timerOptions, добавив больше элементов, и посмотреть, что произошло:

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>

После кнопок у нас есть текстовая метка с надписью Минуты и компонент, управляемый переключателем:

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

Кнопки, которые мы визуализируем в TimerWrapper, берутся из этого компонента. У него есть троичное условие (также известное как оператор Элвиса), чтобы установить либо минуты, умножив их на 60 (60 секунд в минуте), либо секунды:

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

При рендеринге мы используем TouchableOpacity, который функционально похож на TouchableHighlight, но отличается визуальным представлением (он прозрачен при прикосновении). Существует троичное условие для вывода слова «минуты» или «секунды» в зависимости от значения свойства isMinutes:

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

Компонент Timer отображает количество оставшихся секунд, а также воспроизводит звук, когда это число равно 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> ) } })

Объект стилей использует Flex. В контейнере есть flexDirection со значением column. Он позиционирует элементы вертикально, как в столбце. Другое значение - строка, которая расположит их по горизонтали.

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' } })

Наконец, есть оператор реестра:

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

Теперь мы можем установить и импортировать аудиоплеер в проект Xcode, выполнив шаги, описанные в предыдущем разделе. Не забудьте также включить звуковой файл. Когда вы закончите, перейдите в папку ch9 / timer и запустите локальный сервер с помощью $ response-native start. Тебе следует увидеть:

React packager ready.

Зайдите в свой симулятор и обновите его. Вы должны увидеть кнопки с секундами на них и выключатель в выключенном положении. Включите его, чтобы использовать минуты, и кнопки поменяются. При нажатии на 5 минут начнется обратный отсчет, показывающий оставшиеся секунды, как показано на рисунке 9.

Смею вас переделать это маленькое приложение (сделать его красивее!), Опубликовать его в App Store и прислать мне ссылку. Может, тебе удастся попасть в топ-чарты. Flappy Bird сделал.

Проект: приложение "Погода"

Идея этого проекта состоит в том, чтобы получать прогнозы погоды из API OpenWeatherMap на основе названия города, предоставленного пользователем (рисунок 10). В этом проекте мы будем использовать навигатор для переключения между экранами и отображения меню навигации вверху с кнопкой для возврата.

Кроме того, будет функция «запомнить меня», позволяющая сохранить введенное название города для использования в будущем. Сохранение будет реализовано с помощью AsyncStorage.

Полученные данные прогноза будут показаны в сетке с датой, описанием и температурой в F и C, как показано на рисунке 11.

Для начала используйте каркас, предоставляемый инструментом React Native CLI (если у вас нет v0.1.7, следуйте инструкциям в начале этой главы, чтобы получить его):

$ react-native init weather

Команда выведет что-то вроде этого:

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

Откройте проект iOS в Xcode с помощью этой команды:

$ open ios/weather.xcodeproj

В дополнение к уже существующему index.ios.js создайте четыре файла: прогноз.ios.js, search.ios.js, weather-api.js и response.json, чтобы структура проекта выглядела так:

/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

Файлы search.ios.js и прогноз.ios.js будут компонентами для первого экрана, на котором будет поле ввода для названия города, и второго экрана, на котором будет отображаться прогноз, соответственно. Но прежде чем мы начнем реализовывать поиск и прогноз, давайте закодируем компонент приложения и навигацию, которые позволят нам переключаться между экранами поиска и прогноза.

В файле index.ios.js добавьте классы React Native, показанные в следующем листинге. Единственные классы, которые должны быть вам незнакомы, - это AsyncStorage и PixelRatio - все остальное было рассмотрено ранее в этой главе:

'use strict' var React = require('react-native') var { AppRegistry, StyleSheet, Text, View, Navigator, ListView, AsyncStorage, TouchableOpacity, PixelRatio } = React

Импортировать поиск. Константа - это вещь ES6. Вы можете использовать var или узнать о константе в Шпаргалке по ES6 / ES2016.

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

Теперь давайте создадим абстракцию для хранилища, то есть AsyncStorage. Вы можете использовать AsyncStorage напрямую, но лучше иметь абстракцию, подобную показанной здесь. Интерфейс AsyncStorage очень прост. Он использует методы getItem (), removeItem () и setItem (). Я уверен, вы догадались, что они означают. Единственное, что интересно, это то, что для getItem () нам нужно использовать Promise. Идея заключается в том, что результаты getItem () являются асинхронными. Больше о обещаниях ES6 можно найти в шпаргалке.

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 }

Удалите шаблонный компонент и замените его приложением:

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

Компонент приложения должен отображать навигатор. Мы предоставляем компонент Search в качестве начального маршрута:

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

Свойство ref - это то, как мы можем получить доступ к экземпляру Navigator в самом компоненте приложения. Объект навигатора будет в this.refs.navigator, если он относится к приложению:

ref='navigator'

Панель навигации - это меню в верхней части экрана, и мы отображаем его с помощью компонента Navigator.NavigationBar и предоставления свойства routeMapper (нам все еще нужно реализовать это):

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

Хотя панель навигации - полезная, но не обязательная функция, важно следующее свойство.
По сути, оно отображает каждый маршрут. В этом примере я предполагаю, что в аргументе маршрута есть все, что мне нужно, например компоненты и свойства. Другой способ реализовать навигатор - передать в маршруте только идентификаторы и разрешить объект компонента из идентификатора с помощью некоторой хеш-таблицы (т. Е. Объекта стека маршрутов).

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

Вы можете контролировать, где находится объект навигатора в дочерних элементах, задав для него любое свойство, которое вы хотите использовать. Я придерживаюсь его; объект навигатора помещается в this.props.navigator:

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

После того, как мы добавили навигатор и имя, объект props готов к рендерингу:

return React.createElement(route.component, props)

А затем закроем все скобки и теги:

}} /> ) } })

Мы сделали большую часть тяжелой работы. Если вы решили не реализовывать панель навигации, вы можете пропустить NavigationBarRouteMapper. Если вы хотите использовать планку, вы можете реализовать ее следующим образом.

У сопоставителя маршрутов должны быть определенные методы: LeftButton, RightButton и Title. Этот шаблон был вдохновлен официальным примером панели навигации React. Первый метод проверяет, является ли это исходным маршрутом или нет, с условием index == 0. В качестве альтернативы мы можем проверить название сцены, например name == Search.

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

Если мы передаем первое утверждение, мы находимся в прогнозе. Установить предыдущий маршрут (Поиск):

var previousRoute = navState.routeStack[index - 1]

Теперь верните кнопку, которая является компонентом TouchableOpacity с текстом в нем. Я использую угловые скобки с именем предыдущего маршрута в качестве метки кнопки, как показано на рисунке 12. Вы можете использовать Next или что-то еще. Этот компонент навигатора легко настраивается. Скорее всего, у вас тоже были бы красиво оформленные изображения.

return ( <TouchableOpacity

Обработчик событий использует метод pop (). Подобно Array.pop (), он удаляет последний элемент из стека / массива. Последний элемент - это текущий экран, поэтому мы возвращаемся к предыдущему маршруту:

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

В этом проекте правая кнопка нам не нужна, но если она вам нужна, вы можете реализовать ее аналогично левой кнопке. Возможно, вы захотите использовать список маршрутов, чтобы знать, какой из них будет следующим, на основе индекса текущего маршрута.

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

Последний метод прост. Мы отображаем название маршрута как заголовок. Вы можете использовать свойство title вместо name, если хотите; только не забывайте обновлять его везде (то есть в initialRoute, renderScene и push () в поиске).

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

Наконец, стили! Их легко читать. Одно новое дополнение - PixelRatio. Это даст нам соотношение пикселей, чтобы мы могли контролировать значения на более низком уровне:

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' } })

Измените компонент погоды на Приложение в вызове реестра:

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

Мы закончили с одним файлом, осталось еще два. Двигаясь в логической последовательности потока приложения, мы продолжаем поиск по search.ios.js, импортируя объекты:

'use strict' var React = require('react-native') const Forecast = require('./forecast.ios') var { StyleSheet, Text, TextInput, View, Switch, TouchableHighlight, ListView, Alert } = React

Затем мы хотим объявить ключ API OpenWeatherMap, который вы можете получить на их веб-сайте после регистрации в качестве разработчика. Выбирайте бесплатный план, если вы не уверены, что ваше приложение достигнет предела, когда станет номером один в iTunes (или в App Store?). Воздержитесь от использования моих ключей и получите свои:

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

В случае, если OpenWeatherMap изменяет формат ответа или если вы хотите работать в автономном режиме (как это делаю я), оставьте настоящий URL-адрес в комментариях и используйте локальную версию (сервер Node.js weather-api.js):

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

Поскольку этот файл импортируется index.ios.js, нам нужно экспортировать необходимый компонент. Вы можете создать другую переменную / объект, но я просто назначаю компонент для module.exports для красноречия:

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

Когда мы получаем начальное состояние, мы хотим проверить, было ли сохранено название города. Если да, то мы будем использовать это имя и установить isRemember в значение true, потому что название города было запомнено при предыдущем использовании:

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

Пока мы ждем, пока API хранилища выполнит асинхронный обратный вызов с названием города, мы устанавливаем значение none:

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

Затем мы обрабатываем переключатель, устанавливая состояние isRemember, потому что это управляемый компонент:

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

Если вы помните из предыдущих глав (я знаю, это было так давно!), SetState () на самом деле является асинхронным. Мы хотим удалить название города, если помните? toggle выключено, поэтому нам нужно реализовать removeItem () в обратном вызове setState (), а не только в следующей строке (у нас может быть состояние гонки, и состояние будет старым, если мы не будем использовать обратный вызов):

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

При каждом изменении названия города TextInput мы обновляем состояние. Это обработчик onChangeText, поэтому мы получаем значение в качестве аргумента, а не событие:

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

Метод search () запускается кнопкой Search и вводом виртуальной клавиатуры. Во-первых, мы определяем состояния как локальные переменные, чтобы исключить ненужную типизацию:

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

Хорошо бы убедиться, что название города не пустое. Для этого есть кроссплатформенный компонент Alert:

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

Самая интересная часть логики всего приложения - это то, как мы выполняем внешний вызов. Ответ прост. Мы будем использовать новый fetch API, который уже является частью Chrome. Сейчас мы не слишком заботимся о Chrome; все, что нам нужно знать, это то, что React Native поддерживает его. В этом примере я прибег к строковой интерполяции ES6 (также известный как строковый шаблон) для создания URL. Если вы используете локальный сервер, ответ будет таким же (response.json), поэтому URL не имеет значения.

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

Как только мы получим данные, мы хотим сохранить название города. Может быть, вы захотите сделать это перед вызовом выборки. Тебе решать.

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

ListView будет отображать сетку, но для этого требуется специальный источник данных объекта. Создайте это так:

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

Все готово для рендеринга прогноза. Используйте объект Navigator, вызвав push () и передав все необходимые свойства:

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

passProps - произвольное имя. Здесь я следовал синтаксису NavigatorIOS. Вы можете выбрать другое имя. Для ListView мы заполняем строки из массива JavaScript / Node с помощью cloneWithRows ():

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

Мы закончили с методами поиска. Теперь мы можем визуализировать элементы:

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>

Следующим элементом является TextInput для названия города. У него есть два обратных вызова: onChangeText, который запускает handleCityName, и onEndEditing, который вызывает поиск:

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

Последние несколько элементов - это метка переключателя, сам переключатель и кнопка поиска:

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

И, конечно же, стили - без них макет и шрифты будут искажены. Свойства по большей части говорят сами за себя, поэтому мы не будем вдаваться в подробности о них.

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 } })

Итак, мы вызываем метод push () из компонента Search, когда нажимаем Search. Это вызовет событие в элементе Navigator, а именно renderScene, которое отображает прогноз. Давайте реализуем это. Обещаю, мы почти закончили!

Файл прогноз.ios.js начинается с импорта. К настоящему времени, если вам это незнакомо, я бессилен.

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

Я написал эту функцию, в основном для американцев, чтобы вычислить F из C. Возможно, это не очень точно, но пока подойдет:

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

Компонент ForecastRow не имеет состояния (подробнее о компонентах без состояния в главе 10). Его единственная цель - отобразить один элемент прогноза:

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

В строке мы выводим дату (dt_txt), описание (дождливо или солнечно) и температуру в градусах Цельсия и F (рисунок 9-X). Последнее достигается путем вызова функции fToC, определенной ранее в этом файле:

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

Результат будет выглядеть, как показано на рисунке 9-X.

Затем мы экспортируем компонент Forecast, который представляет собой ScrollView с текстом и ListView:

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

ListView принимает свойства dataSource и renderRow для визуализации сетки. Источник данных должен быть особого типа. Это не может быть простой массив JavaScript / узлов:

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

И стили. Тадаах!

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' } })

Последний штрих - если вы работаете в автономном режиме и используете локальный URL. Вам необходимо иметь два файла:

  1. response.json - ответ на настоящий вызов API для Лондона
  2. weather-api.js - Ультра-минималистичный веб-сервер Node, который принимает response.json и передает его клиенту.

Скопируйте response.json с GitHub. Затем реализуйте этот сервер Node.js, используя только основные модули (мне нравятся Express или Swagger, но их использование здесь - излишество):

var http = require('http'), forecastData = require('./response.json') http.createServer(function(request, response){ response.end(JSON.stringify(forecastData)) }).listen(3000)

Запустите сервер с помощью $ node weather-api, свяжите код React Native с запуском $ response-native и перезагрузите Simulator. Сборщик и сервер должны работать вместе, поэтому вам может потребоваться открыть новую вкладку или окно в приложении терминала / iTerm.

Примечание. Если вы получаете сообщение об ошибке «Неизменяемое нарушение: обратный вызов с идентификатором 1–5», убедитесь, что отладчик Chrome не открывался более одного раза.

Вы должны увидеть пустое поле с названием города. Ничего страшного, потому что вы запускаете приложение впервые. Я намеренно оставил логи в реализации хранилища. Вы должны увидеть следующее, когда откроете DevTools на вкладке Chrome для отладки React Native (обычно он открывается автоматически, когда вы включаете его, перейдя в меню «Оборудование» - ›Жесты встряхивания-› Отладка в Chrome - не то, чтобы вы собирались встряхнуть свой ноутбук! ):

AsyncStorage GET for cityName: "null"

Поиграйте с переключателем, введите имя (рисунок 13) и получите прогноз погоды. Приложение готово. Бум! Теперь поместите на него красивый интерфейс и отправьте его!

Контрольный опрос

  1. Как создать новый проект React Native: создать файлы вручную или запустить $ npm init, $ response-native init или $ response native init?
  2. Какой тип данных принимает ListView: массив, объект или источник данных? (Источник данных)
  3. Одно из преимуществ React Native по сравнению с собственной разработкой заключается в том, что React Native имеет возможность перезагрузки в реальном времени. Правда или ложь? (Правда)
  4. Вы можете использовать любой CSS в стилях объекта React Native StyleSheet. Правда или ложь? (Ложь)
  5. В каком файле Objective C можно переключить расположение пакета React Native: bundle.cc, AppDelegate.m, AppDelegate.h, package.json или index.ios.js? (AppDelegate.m)

Действия

Обучение просто чтением не так эффективно, как обучение путем чтения, а затем действия. да. Даже такая хорошая книга. Так что действуйте СЕЙЧАС, чтобы закрепить знания.

  • Посмотрите скринкасты React Native Quickly на Node.Unversity, которые проведут вас через приложение Погода.
  • Запустите Weather и Timer на своем компьютере из исходного кода
  • Измените текст, например метки кнопок или названия меню, просмотрите результаты в симуляторе
  • Изменить звуковой файл в таймере
  • Добавить геолокацию в Погода (см. Геолокация)

Резюме

Это была краткая книга, но мы охватили не один, а два проекта. В дополнение к этому мы также рассмотрели:

  • Как React Native приклеивается к коду Objective C в проектах Xcode
  • Основные компоненты, такие как View, Text, TextInput, Touchables и ScrollView
  • Реализация приложения с помощью навигатора
  • Как сохранить данные локально на устройстве
  • Использование API выборки для связи с внешним сервером HTTP REST API (вы можете использовать тот же метод для сохранения данных на внешнем сервере либо для входа в систему или выхода из системы)

React Native - потрясающая технология. Я был действительно приятно удивлен, когда начал изучать и использовать его. Есть много свидетельств того, что React Native может де-факто стать следующим способом разработки мобильных приложений. Функция перезагрузки в реальном времени может позволить разработчикам загружать код в свои приложения, не отправляя их повторно в App Store - круто, правда?

Ответы на викторину

  1. $ response-native init, потому что создание файла вручную утомительно и подвержено ошибкам
  2. Источник данных
  3. Правда
  4. Ложь
  5. AppDelegate.m

-
Азат Мардан

Https://www.linkedin.com/in/azatm
Чтобы связаться с Азатом, основным автором этого блога, заполните контактную форму.

Кроме того, не забудьте получить 3 замечательных ресурса БЕСПЛАТНО при подписке на рассылку новостей.
Просто.
Легко.
Никаких обязательств.

Первоначально опубликовано на сайте webapplog.com 19 сентября 2016 г.