[Перевод] Создаем приложение на JavaScript с помощью React Native
В этом уроке мы будем изучать React Native — фреймворк от компании Facebook для создания нативных приложений под iOS и Android. У него много общего с другим очень популярным фреймворком от Facebook — React Javascript, который предназначен для построения декларативных пользовательских интерфейсов.
Примечание: это обновленный вариант урока, написанного Колином Эбергардом из iOS Team, содержащий ряд правок для версии React Native 0.22.
Но на данный момент и так существует достаточно фреймворков, использующих JavaScript для создания iOS-приложений, таких как PhoneGap или Titanium. Что же делает React Native особенным?
1. В отличие от PhoneGap, в React Native логика приложения пишется и работает на JavaScript, в то время как его интерфейс остается полностью нативным. Таким образом не требуется никаких компромиссов, характерных для HTML5 UI.
2. В отличие от Titanium, React вводит новый оригинальный и крайне эффективный подход к созданию пользовательских интерфейсов. Если говорить кратко, UI приложения выражается как функция текущего состояния приложения.
Ключевая особенность React Native в том, что его разработчики намерены привнести модель программирования React в сферу разработки мобильных приложений. Важное уточнение: речь идет не о таком кроссплатформенном инструменте, с которым можно писать софт один раз и использовать его везде, а о таком, который можно изучить один раз и писать на нем везде. Данный урок предназначен для iOS-платформы, но, изучив весь изложенный материал, вы сможете без труда создавать также Android-приложения.
Если у вас есть опыт написания приложений на Objective-C или Swift, вы наверняка не обрадуетесь идее перехода на JavaScript. Но вместе с тем, второй пункт явно должен был заинтересовать Swift-разработчиков.
Несомненно, работая со Swift, вам приходилось изучать много новых и более эффективных способов шифрования алгоритмов, а также методик, способствующих преобразованию и неизменяемости. Тем не менее, способ построения UI здесь очень похож на тот, что используется при работе с Objective-C: он тоже основывается на UIKit и является императивным.
React за счет таких необычных понятий как Virtual DOM и согласование переносит функциональное программирование на слой пользовательского интерфейса.
В данном уроке по React Native мы будем создавать приложение по поиску недвижимости в Великобритании:
Если вы никогда прежде не работали с JavaScript, не волнуйтесь. Мы подробно разберем каждый шаг разработки. React использует для стилизации синтаксис наподобие CSS, который легко прочитать и понять, но в случае чего вы всегда можете обратиться к Mozilla Developer Network.
Интересно? Идем дальше.
Приступаем к работе
Для создания JavaScript-кода React Native использует Node.js, среду выполнения JavaScript. Если вы еще не установили себе Node.js, пора это сделать.
Сначала установим Homebrew, следуя инструкциям на сайте, а затем — Node.js, выполнив в окне терминала следующее:
brew install node
Затем с помощью homebrew установим watchman — сервис для отслеживания изменения и поиска файлов от Facebook:
brew install watchman
React Native использует его, чтобы отслеживать изменения кода и делать соответствующие правки. Это что-то вроде Xcode, но выполняющего сборку каждый раз после сохранения файла.
Далее установим React Native Command Line Interface (CLI), используя npm:
npm install -g react-native-cli
Он использует Node Package Manager, чтобы вызвать и глобально установить CLI-инструмент; npm поставляется вместе с Node.js, его функция аналогична CocoaPods или Carthage.
Для тех, кто хочет глубже разобраться с React Native, его исходный код находится в открытом доступе на GitHub.
Перейдите к папке, в которой вы хотите сохранить проект, и воспользуйтесь CLI-инструментом для его создания:
react-native init PropertyFinder
Эта строка создает начальный проект, в котором содержится всё необходимое для разработки и запуска приложения на React Native.
Если вы видите уведомление об устаревшей версии Node.js, убедитесь, что та, которую установил brew, является актуальной. Для этого выполните в терминале команду brew link --overwrite node.
Взглянув на созданные папки и файлы, вы обнаружите папку node_modules, в которой находится фреймворк React Native. Файл index.ios.js — это макет приложения, созданный CLI-инструментом. Обратите также внимание на папку ios — в ней содержится проект Xcode и небольшой код для интеграции с Bootstrap. Наконец, там есть и компоненты для Android, но мы не будем рассматривать их здесь.
Откройте файл проекта, сделайте его сборку и запустите. Симулятор отобразит следующее сообщение:
Примечание: На момент написания урока начальный проект, созданный CLI-инструментом React Native, выводил три предупреждения во время сборки. Потому не волнуйтесь, впервые увидев какие-либо уведомления от Xcode. Разработчики React Native знают об этой небольшой проблеме, и мы работаем вместе с ними над ее устранением в следующем релизе React Native.
Вероятно, вы также заметили всплывающее окно терминала с таким сообщением:
┌──────────────────────────────────────────────┐
│ 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/tomelliott/Desktop/Scratch/PropertyFinder
[6:15:40 PM] Building Dependency Graph
[6:15:40 PM] Crawling File System
[6:15:40 PM] Loading bundles layout
[6:15:40 PM] Loading bundles layout (0ms)
[Hot Module Replacement] Server listening on /hot
React packager ready.
[6:15:41 PM] Crawling File System (747ms)
[6:15:41 PM] Building in-memory fs for JavaScript
[6:15:42 PM] Building in-memory fs for JavaScript (653ms)
[6:15:42 PM] Building in-memory fs for Assets
[6:15:42 PM] Building in-memory fs for Assets (277ms)
[6:15:42 PM] Building Haste Map
[6:15:42 PM] Building (deprecated) Asset Map
[6:15:42 PM] Building (deprecated) Asset Map (49ms)
[6:15:42 PM] Building Haste Map (400ms)
[6:15:42 PM] Building Dependency Graph (2094ms)
Это упаковщик React Native, работающий под управлением Node.js. Вскоре вы узнаете, для чего он нужен.
Не закрывайте окно терминала, пусть оно работает на фоне. Если вы случайно закрыли его, просто остановите и перезапустите проект с помощью Xcode.
Примечание: прежде чем мы заберемся в дебри кода, нужно определиться с выбором текстового редактора. Вам предстоит писать много JavaScript-кода, а Xcode явно не подходит для этого. Я использую Sublime Text, это недорогой и очень удобный инструмент. Но Atom, Brackets или любой другой легковесный редактор тоже отлично подойдет.
Hello React Native
Прежде чем начать работу над приложением по поиску недвижимости, мы создадим приложение Hello World!… По ходу дела я буду вводить новые компоненты и понятия.
Откройте файл index.ios.js в текстовом редакторе и удалите всё его содержимое, так как мы будем создавать приложение с нуля. Добавьте в начале файла следующее:
'use strict';
Эта директива объявляет строгий режим, который добавляет улучшенную обработку ошибок и налагает ограничения на некоторые элементы JavaScript. Проще говоря, он улучшает работу JavaScript.
Примечание: более подробную информацию о строгом режиме можно найти в статье Джона Резига под названием ECMAScript 5 Strict Mode, JSON, and More.
Затем добавьте эту строку:
var React = require('react-native');
Она загружает модуль react-native и присваивает его переменной React. React Native использует такую же технологию загрузки модуля, как и Node.js с функцией require, которая примерно эквивалентна подключению и импорту библиотек в Swift.
Примечание: более подробную информацию о модулях JavaScript можно найти в статье Эдди Османи о модульном JavaScript.
Далее добавьте следующее:
var styles = React.StyleSheet.create({
text: {
color: 'black',
backgroundColor: 'white',
fontSize: 30,
margin: 80
}
});
Этот код задает единый стиль, который мы вскоре применим к тексту Hello World!… Если у вас уже есть какой-либо опыт веб-разработки, вероятно, вы узнали эти свойства. Внешний вид класса StyleSheet, используемого для стилизации интерфейса, напоминает синтаксис широко применяемого в вебе языка Cascading Style Sheets (CSS).
Итак, займемся непосредственно приложением. Добавьте следующий код прямо под переменной со стилями:
class PropertyFinderApp extends React.Component {
render() {
return React.createElement(React.Text, {style: styles.text}, "Hello World!");
}
}
Да, это класс JavaScript.
Классы были добавлены в ECMAScript 6 (ES6). Поскольку JavaScript постоянно развивается, разработчики вынуждены ограничивать себя в используемых средствах ради сохранения совместимости со старыми системами или браузерами. И хотя iOS 9 не полностью поддерживает ES6, React Native использует инструмент под названием Babel, который автоматически переводит современный JavaScript в совместимый с устаревшими версиями JavaScript там, где это необходимо.
Примечание: если вы веб-разработчик, вы также можете использовать Babel в браузере. Так что теперь действительно не осталось оправданий для работы со старыми версиями JavaScript — даже для поддержки устаревших версий браузеров.
PropertyFinderApp расширяет React.Component, основной структурный элемент интерфейса React. Компоненты содержат неизменяемые свойства и изменяемые переменные состояния; они предоставляют метод для рендеринга. Приложение, над которым мы сейчас работаем, очень простое и нуждается только в методе рендеринга.
Компоненты React Native — это не классы UIKit, а их легковесные эквиваленты. Фреймворк обеспечивает преобразование дерева компонентов React в требуемый нативный интерфейс.
Наконец, добавим в конец файла эту строку:
React.AppRegistry.registerComponent('PropertyFinder', function() { return PropertyFinderApp });
AppRegistry определяет точку входа в приложение и предоставляет корневой компонент.
Сохраните изменения в index.ios.js и вернитесь к Xcode. Убедитесь, что схема PropertyFinder выбрана с одним из симуляторов iPhone, а затем соберите и запустите ваш проект. Через несколько секунд на экране отобразится ваше приложение Hello World!:
Это JavaScript-приложение, работающее на симуляторе, который отображает нативный UI — и это без помощи браузера.
Всё еще не верите? Убедитесь сами: выберите в Xcode Debug\View Debugging\Capture View Hierarchy и вы увидите нативную иерархию представлений. Вы также заметите повсюду сущности UIWebView. Тест приложения отображается в RCTText. Но что это такое? Вернитесь в Xcode, выберите File\Open Quickly… и введите RCTView.h. Обратите внимание, что RCTView наследует непосредственно от UIView. Выходит, всё работает отлично.
Хотите знать, как это работает? Откройте в Xcode AppDelegate.m и определите расположение application: didFinishLaunchingWithOptions: . Этот метод создает RCTRootView, который загружает JavaScript-приложение и рендерит результирующее представление.
Когда приложение запускается, RCTRootView загружает приложение из этого URL:
http://localhost:8081/index.ios.bundle
Вспомните окно терминала, которое было открыто, когда вы запускали это приложение. Оно запускает упаковщик и сервер, который обрабатывает запрос выше.
Откройте этот URL в Safari, и вы увидите JavaScript-код вашего приложения. Вы также должны обнаружить там код Hello World!, встроенный во фреймворк React Native.
Когда ваше приложение запускается, этот код загружается и выполняется фреймворком JavaScriptCore. В нашем случае он загружает компонент PropertyFinderApp и затем выстраивает нативное UIKit представление. Дальше в уроке мы поговорим об этом подробнее.
Hello World JSX
Созданное приложение использует React.createElement для построения простого интерфейса, преобразовываемого в нативный эквивалент с помощью React. И хотя текущий JavaScript-код читается легко, в случае более сложного UI со вложенными элементами он может превратиться в кашу.
Убедитесь, что приложение еще работает, затем вернитесь к редактированию файла index.ios.js и измените оператор return следующим образом:
return Hello World (Again) ;
Это JSX, расширение синтаксиса JavaScript, которое добавляет в JavaScript-код синтаксис наподобие HTML. Те, у кого уже есть опыт веб-разработки, заметят сходство с последним. Мы будем использовать JSX на протяжении всего урока.
Сохраните изменения в index.ios.js и вернитесь в симулятор. Нажмите Cmd+R, чтобы обновить сообщение на экране:
Перезапустить приложение на React Native так же просто, как обновить страницу браузера. Обратите внимание, что в таком случае отобразятся только те изменения, которые касались JavaScript-файлов. Во всех других случаях потребуется повторная сборка приложения в Xcode.
Поскольку в этом уроке мы будем работать с тем же набором JavaScript-компонентов, вы можете оставить приложение работать и обновлять его после сохранения изменений в index.ios.js.
Примечание: если вам интересно, во что преобразовывается JSX, взгляните на «bundle» в браузере.
Полагаю, мы вполне наигрались с Hello World!, теперь пришло время создать настоящее приложение.
Добавляем навигацию
Приложение Property Finder использует стандартную стековую навигацию, предоставленную навигационным контроллером UIKit. Добавим это поведение.
В файле index.ios.js переименуйте класс PropertyFinderApp в HelloWorld:
class HelloWorld extends React.Component {
Оставим пока текст Hello World!, но он больше не будет корневым компонентом приложения.
Затем добавим ниже компонента HelloWorld следующий класс:
class PropertyFinderApp extends React.Component {
render() {
return (
);
}
}
Он создает навигационный контроллер, применяет стиль и устанавливает первоначальный маршрут к компоненту HelloWorld. В веб-разработке маршрутизация — это способ определения навигационной структуры приложения, где страницы — или маршруты — привязываются к соответствующим URL.
Далее подкорректируйте стили, добавив туда параметры контейнера, как показано ниже:
var styles = React.StyleSheet.create({
text: {
color: 'black',
backgroundColor: 'white',
fontSize: 30,
margin: 80
},
container: {
flex: 1
}
});
О том, что такое flex: 1, вы узнаете немного позже.
Сохраните изменения, вернитесь в симулятор и нажмите Cmd+R, чтобы увидеть обновленный интерфейс:
Корневое представление навигационного контроллера соответствует тексту Hello World!… Теперь у нас есть базовая навигационная структура текущего приложения. Пора добавить настоящий UI.
Создаем страницу поиска
Добавьте в проект новый файл под названием SearchPage.js и поместите его в одну папку с файлом index.ios.js. Добавьте в новый файл этот код:
'use strict';
var React = require('react-native');
var {
StyleSheet,
Text,
TextInput,
View,
TouchableHighlight,
ActivityIndicatorIOS,
Image,
Component
} = React;
Мы уже рассматривали строгий режим и импорт в react-native, но следующий оператор присвоения — это нечто другое.
Это деструктурирующее присваивание, позволяющее извлекать множество свойств объекта и присваивать их переменным с одним оператором. Как результат, в оставшемся коде можно отбросить префикс React. К примеру, можно обращаться напрямую к StyleSheet, а не к React.StyleSheet. Деструктурирование также очень удобно использовать для управления массивами. Более подробную информацию о нем можно найти в этой статье.
Не закрывая файл SearchPage.js, добавьте внизу этот стиль:
var styles = StyleSheet.create({
description: {
marginBottom: 20,
fontSize: 18,
textAlign: 'center',
color: '#656565'
},
container: {
padding: 30,
marginTop: 65,
alignItems: 'center'
}
});
Это тоже стандартные CSS-свойства. Возможно, данный способ задания стилей покажется вам менее удобным, чем использование Interface Builder, но этот подход определенно лучше, чем задавать свойства представления по одному в методах viewDidLoad ().
Вставьте сам компонент непосредственно под стилями:
class SearchPage extends Component {
render() {
return (
Search for houses to buy!
Search by place-name, postcode or search near your location.
);
}
}
render отлично демонстрирует JSX и его структуру. Наряду со стилем вы можете очень просто визуализировать интерфейс, созданный этим компонентом: контейнер с двумя текстовыми подписями.
Напоследок добавим следующую строку в конец файла:
module.exports = SearchPage;
Она экспортирует класс SearchPage, что позволяет использовать его в других файлах.
Следующий шаг — обновить маршрутизацию приложения, чтобы установить другой первоначальный маршрут.
Откройте index.ios.js и добавьте эту строку сразу после require в начале файла:
var SearchPage = require('./SearchPage');
В функции render класса PropertyFinderApp обновите initialRoute, чтобы привязать только что созданную страницу, как показано ниже:
component: SearchPage
Теперь, если хотите, можно удалить класс HelloWorld и его стили. Они вам больше не понадобятся.
Сохраните изменения, вернитесь в симулятор и нажмите Cmd+R, чтобы увидеть обновленный интерфейс:
Здесь используется новый компонент SearchPage.
Стилизуем с помощью Flexbox
В этом уроке мы уже имели дело с некоторыми базовыми свойствами CSS, задающими параметры цвета, а также внутренних и внешних отступов. Тем не менее, вероятно, вы еще не слышали о flexbox. Эта технология лишь недавно была добавлена в спецификацию CSS, она очень полезна при построении макетов пользовательских интерфейсов.
React Native использует библиотеку css-layout, которая является JavaScript-реализацией flexbox-стандарта, скомпилированного на C (для iOS) и на Java (для Android).
Очень хорошо, что React Native создавался как отдельный проект, нацеленный на несколько языков программирования, так как это позволяет разрабатывать приложения с использованием новейших подходов, вроде применения flexbox-макетов к SVG.
По умолчанию контейнер в вашем приложении имеет направление потока данных в виде столбца, что соответствует параметру column –, а значит, всё содержимое контейнера будет выстраиваться вертикально:
Это так называемая главная ось, или main axis, она может иметь как горизонтальное, так и вертикальное направление.
Вертикальное положение каждого дочернего элемента контейнера высчитывается с учетом его внешних и внутренних отступов, а также высоты. Контейнер также задает свойству alignItems значение center, что определяет положение дочерних элементов на главной оси. В данном случае мы получим текст с выравниванием по центру.
Теперь добавим поле ввода и кнопки. Откройте файл SearchPage.js и введите следующий код сразу после закрывающего тега второго элемента Text:
Go
Location
Мы добавили два представления высшего уровня: в одном из них находится текстовое поле ввода и кнопка, а в другом — еще одна кнопка. Сейчас вы узнаете, как можно стилизовать эти элементы
Вернитесь к параметрам стилей, поставьте запятую после блока container и добавьте ниже новые стили:
flowRight: {
flexDirection: 'row',
alignItems: 'center',
alignSelf: 'stretch'
},
buttonText: {
fontSize: 18,
color: 'white',
alignSelf: 'center'
},
button: {
height: 36,
flex: 1,
flexDirection: 'row',
backgroundColor: '#48BBEC',
borderColor: '#48BBEC',
borderWidth: 1,
borderRadius: 8,
marginBottom: 10,
alignSelf: 'stretch',
justifyContent: 'center'
},
searchInput: {
height: 36,
padding: 4,
marginRight: 5,
flex: 4,
fontSize: 18,
borderWidth: 1,
borderColor: '#48BBEC',
borderRadius: 8,
color: '#48BBEC'
}
Следите за форматированием: каждое свойство стиля или селектор следует отделять запятой.
Эти стили предназначены для только что добавленных поля ввода и кнопок.
Сохраните изменения, вернитесь в симулятор и нажмите Cmd+R, чтобы увидеть обновленный интерфейс:
Текстовое поле и кнопка «Go» находятся на одной строке, так как вы поместили их в контейнер со стилем flowRight, элементы которого выстраиваются в строку за счет свойства flexDirection: 'row'. Вместо того чтобы жестко задавать ширину каждого из этих элементов, мы выставили им относительную ширину с помощью значений свойства flex. Таким образом, в селекторе текстового поля searchInput мы имеем flex: 4, а в селекторе кнопки button — flex: 1, вследствие чего их соотношение составляет 4:1.
Возможно, вы также заметили, что элементы, которые мы называем кнопками, по сути таковыми не являются. На самом деле, кнопки в UIKit — это всего лишь интерактивные текстовые надписи. Кнопки вашего приложения используют компонент React Native под названием TouchableHighlight, который по нажатию становится прозрачным и показывает нижележащий цвет.
Наконец, добавим на страницу поиска изображение. Вы можете скачать его в нескольких разрешениях одним архивом. После скачивания распакуйте архив.
Далее создадим в корневом проекте директорию под названием «Resources» и поместим в нее все три изображения.
Предметные каталоги: Как вам известно, специалисты из Apple рекомендуют по возможности помещать изображения в предметные каталоги. Тем не менее, для React Native это наоборот нежелательно. Хранение цифровых объектов приложения рядом с его компонентами дает несколько преимуществ. Во-первых, это позволяет сохранить независимость компонентов. Во-вторых, при добавлении новых изображений не требуется повторная загрузка приложения. И в-третьих, при разработке приложения для iOS и Android это дает возможность хранить изображения для двух платформ в одном месте.
Вернитесь к файлу SearchPage.js и добавьте эту строку под закрывающим тегом компонента TouchableHighlight, отвечающего за кнопку location:
Теперь добавьте соответствующий стиль изображения в блоке со стилями, не забыв поставить запятую после предыдущего селектора:
image: {
width: 217,
height: 138
}
Сохраните изменения. Вернитесь в симулятор и нажмите Cmd+R, чтобы увидеть новый интерфейс:
Примечание: Если изображение с домом не отображается, а вместо него показано уведомление о том, что картинка не найдена, попробуйте перезапустить упаковщик с помощью команды npm start в терминале.
Наше приложение уже смотрится симпатично, но всё же чего-то не хватает. Нужно добавить состояние приложения и выполнить кое-какие действия.
Добавляем состояние компонента
Каждый компонент в React имеет собственный объект состояния, который используется как хранилище типа «ключ–значение». Прежде чем компонент отобразится, нужно задать начальное состояние.
В файле SearchPage.js добавьте следующий код в класс SearchPage, прямо перед render ():
constructor(props) {
super(props);
this.state = {
searchString: 'london'
};
}
Теперь у вашего компонента есть переменная state, а начальным значением searchString является london.
Время воспользоваться этим состоянием компонента. Изменим элемент TextInput в render, как показано ниже:
Мы выставили значение свойства TextInput — то есть показываемого пользователю текста — на текущее значение переменной состояния searchString. Итак, мы позаботились о начальном состоянии. Но что будет, когда пользователь отредактирует этот текст?
Прежде всего, создадим метод, выступающий в роли обработчика событий. Перейдите к классу SearchPage и добавьте данный метод сразу после constructor:
onSearchTextChanged(event) {
console.log('onSearchTextChanged');
this.setState({ searchString: event.nativeEvent.text });
console.log(this.state.searchString);
}
Он берет значение из свойства text в событии родного браузера и использует его для обновления состояния компонента. Он также добавляет код для сбора данных, которые нам вскоре пригодятся.
Чтобы данный метод вызывался каждый раз при изменении текста, вернемся к полю TextInput метода render и добавим свойство onChange. В итоге тег примет следующий вид:
Когда пользователь меняет текст, вызывается функция, добавленная к свойству onChange (в данном случае это onSearchTextChanged).
Примечание: Возможно, вам непонятно, для чего нужно выражение bind (this). JavaScript интерпретирует ключевое слово this немного иначе, чем большинство других языков. В Swift данному слову соответствует self. Использование bind в данном контексте гарантирует, что this внутри метода onSearchTextChanged является отсылкой к экземпляру компонента. Дополнительную информацию о ключевом слове this можно получить на MDN.
Прежде чем мы снова обновим приложение, добавим оператор log в начале render (), сразу перед return:
console.log('SearchPage.render');
Вам предстоит узнать нечто очень любопытное об этих операторах.
Сохраните изменения, вернитесь в симулятор и нажмите Cmd+R. Вы увидите, что теперь начальным значением поля ввода является «london», а при редактировании текста в консоль Xcode записываются какие-то выражения:
Если вы внимательно посмотрите на скриншот выше, то можете заподозрить, что порядок записи выражений немного нарушен:
1. Это первоначальный вызов render (), необходимый чтобы настроить представление.
2. При изменении текста вызывается onSearchTextChanged ().
3. Затем обновляется состояние компонента, чтобы отобразить только что введенный текст, который повторно запускает render.
4. onSearchTextChanged () завершает всё, записывая новую строку поиска.
Когда приложение обновляет состояние любого компонента React, это запускает повторный рендеринг всего пользовательского интерфейса, который, в свою очередь, вызывает render всех компонентов. Это превосходная идея, так как в этом случае логика рендеринга полностью отделяется от изменений состояния, которые затрагивают UI.
В большинстве UI-фреймворков разработчику нужно либо вручную обновлять интерфейс в зависимости от изменений состояния, либо делать это посредством каких-либо вспомогательных фреймворков, которые создают неявную связь между состоянием приложения и его представлением в UI. Что касается второго варианта, по этому поводу можно ознакомиться со статьей о применении шаблона MVVM совместно с фреймворком ReactiveCocoa.
С React вам больше не придется волноваться о том, какие части UI могут быть затронуты изменением состояния, так как весь интерфейс выражается в виде функции состояния приложения.
На данном этапе вы, скорее всего, заметили один изъян данного подхода. Совершенно верно, дело в производительности.
Конечно, нельзя просто так отбрасывать весь интерфейс и перестраивать его каждый раз, когда что-то меняется. Вот где React по-настоящему проявляет себя. Каждый раз, когда интерфейс рендерится, он берет дерево видимых объектов, которое возвращают методы render, и согласовывает его с текущим представлением UIKit. В результате этого согласования получается список обновлений, которые React должен применить к текущему представлению. Таким образом, повторному рендерингу подвергнутся только те объекты, которые на самом деле были изменены.
Разве не здорово видеть, как в нашем iOS-приложении применяются новейшие подходы, за счет которых ReactJS является таким уникальным: virtual-DOM (объектная модель документа, визуальное дерево веб-документа) и согласование?
Позже у вас будет возможность разобраться с этим подробнее, а пока нужно еще поработать над приложением. Для начала удалим код сбора данных, который мы добавили ранее, так как он только засоряет общий код.
Настраиваем поиск
Чтобы реализовать поиск, нужно обработать нажатие кнопки «Go», создать необходимый API-запрос и предоставить пользователю визуальное подтверждение того, что запрос обрабатывается.
Откройте файл SearchPage.js, найдите constructor и обновите внутри него начальное состояние:
this.state = {
searchString: 'london',
isLoading: false
};
Новое свойство isLoading будет следить за тем, обрабатывается ли запрос.
Добавьте такую логику в начале render:
var spinner = this.state.isLoading ?
( ) :
( );
Это тернарный оператор if, который либо добавляет индикатор выполнения действия, либо отображает пустой экран — в зависимости от состояния компонента isLoading. Поскольку весь компонент рендерится каждый раз заново, вы можете спокойно смешивать логику JSX и JavaScript.
Чтобы добавить на страницу индикатор загрузки, перейдите к JSX, отвечающему за интерфейс поиска в return, и вставьте под Image эту строку:
{spinner}
Затем добавьте данные методы в класс SearchPage:
_executeQuery(query) {
console.log(query);
this.setState({ isLoading: true });
}
onSearchPressed() {
var query = urlForQueryAndPage('place_name', this.state.searchString, 1);
this._executeQuery(query);
}
Метод _executeQuery () впоследствии будет выполнять запрос, но пока он просто заносит сообщение в консоль и необходимым образом настраивает компонент isLoading, чтобы в интерфейсе отобразилось новое состояние.
Примечание: Классы в JavaScript не имеют модификаторов доступа, потому ключевого слова «private» для них тоже не существует. В силу этого многие разработчики часто дают методам префикс в виде нижнего подчеркивания, чтобы указать, что они являются private.
Метод onSearchPressed () настраивает и отправляет запрос. Он должен срабатывать по нажатию кнопки «Go». Чтобы реализовать это, вернитесь к render и добавьте следующее свойство внутри открывающего тега компонента TouchableHighlight, отвечающего за текст «Go»:
onPress={this.onSearchPressed.bind(this)}
Наконец, добавьте эту служебную функцию над объявлением класса SearchPage:
function urlForQueryAndPage(key, value, pageNumber) {
var data = {
country: 'uk',
pretty: '1',
encoding: 'json',
listing_type: 'buy',
action: 'search_listings',
page: pageNumber
};
data[key] = value;
var querystring = Object.keys(data)
.map(key => key + '=' + encodeURIComponent(data[key]))
.join('&');
return 'http://api.nestoria.co.uk/api?' + querystring;
};
Эта функция не зависит от SearchPage, потому она реализуется скорее как свободная функция, а не как метод. Сначала она создает строку запроса, основываясь на параметрах данных. Исходя из них, она преобразовывает данные в требуемый формат строки: пары name=value, разделенные амперсандами. Синтаксис => соответствует стрелочной функции, еще одному недавнему дополнению в JavaScript. Стрелочные функции предоставляют более лаконичный синтаксис для создания анонимных функций.
Сохраните изменения, вернитесь к симулятору, перезагрузите страницу с помощью комбинации Cmd+R и нажмите кнопку «Go». На экране отобразится индикатор загрузки. Обратите внимание на консоль Xcode:
На экране показан индикатор активности, а в логе появляется URL для требуемого запроса. Откройте этот URL в браузере, чтобы увидеть результат: большой JSON-объект. Но не волнуйтесь, вам не нужно будет вникать в него. Сейчас мы добавим код и проведем его парсинг.
Примечание: Данное приложение использует для поиска недвижимости Nestoria API. JSON-ответ, который приходит из API, весьма незамысловат. Так или иначе, вы всегда можете просмотреть документацию на предмет предполагаемого формата URL-запросов и ответов.
Следующий шаг — выполнить запрос из приложения.
Выполняем API-запрос
Не закрывая файл SearchPage.js, обновите начальное положение в конструкторе класса, чтобы добавить переменную message:
this.state = {
searchString: 'london',
isLoading: false,
message: ''
};
Найдите render и добавьте внутри него следующую строку:
{this.state.message}
Она отвечает за отображение внизу экрана сообщений для пользователя.
Найдите класс SearchPage и добавьте в конец метода _executeQuery () данный код:
fetch(query)
.then(response => response.json())
.then(json => this._handleResponse(json.response))
.catch(error =>
this.setState({
isLoading: false,
message: 'Something bad happened ' + error
}));
Он использует функцию fetch, которая является частью Web API и предоставляет значительно улучшенный API по сравнению с XMLHttpRequest. Асинхронный ответ возвращается в виде объекта promise, и в случае успеха выполняется парсинг JSON-объекта, который затем передается в метод _handleResponse (мы добавим его немного позже).
Наконец, добавим эту функцию в SearchPage:
_handleResponse(response) {
this.setState({ isLoading: false , message: '' });
if (response.application_response_code.substr(0, 1) === '1') {
console.log('Properties found: ' + response.listings.length);
} else {
this.setState({ message: 'Location not recognized; please try again.'});
}
}
Она очищает isLoading и при успешном запросе добавляет в лог количество найденных объектов недвижимости.
Примечание: В Nestoria API есть ряд весьма полезных кодов ответа сервера. К примеру, коды 202 и 200 возвращают список локаций, подобранных по принципу наилучшего выбора. Почему бы не воспользоваться этой опцией и не предоставить пользователям несколько дополнительных предложений?
Сохраните изменения, вернитесь в симуля