Створюємо програма на 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 while running on developing 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] <START> Building Dependency Graph
[6:15:40 PM] <START> Crawling File System
[6:15:40 PM] <START> Loading bundles layout
[6:15:40 PM] <END> Loading bundles layout (0ms)
[Hot Module Replacement] Server listening on /hot

React packager ready.

[6:15:41 PM] <END> Crawling File System (747ms)
[6:15:41 PM] <START> Building in-memory fs for JavaScript
[6:15:42 PM] <END> Building in-memory fs for JavaScript (653ms)
[6:15:42 PM] <START> Building in-memory fs for Assets
[6:15:42 PM] <END> Building in-memory fs for Assets (277ms)
[6:15:42 PM] <START> Building Haste Map
[6:15:42 PM] <START> Building (deprecated) Asset Map
[6:15:42 PM] <END> Building (deprecated) Asset Map (49ms)
[6:15:42 PM] <END> Building Haste Map (400ms)
[6:15:42 PM] <END> 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 <React.Text style={styles.text}>Hello World (Again)</React.Text>;

Це 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 (
<React.NavigatorIOS
style={styles.container}
initialRoute={{
title: 'Property Finder',
component: HelloWorld,
}}/>
);
}
}

Він створює навігаційний контролер, застосовує стиль і встановлює початковий маршрут до компоненту 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 (
<View style={styles.container}>
<Text style={styles.description}>
Search for houses to buy!
</Text>
<Text style={styles.description}>
Search by place-name, postcode or search near your location.
</Text>
</View>
);
}
}


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:

<View style={styles.flowRight}>
<TextInput
style={styles.searchInput}
placeholder='Search via name or postcode'/>
<TouchableHighlight style={styles.button}
underlayColor='#99d9f4'>
<Text style={styles.buttonText}>Go</Text>
</TouchableHighlight>
</View>
<TouchableHighlight style={styles.button}
underlayColor='#99d9f4'>
<Text style={styles.buttonText}>Location</Text>
</TouchableHighlight>

Ми додали два подання вищого рівня: в одному з них знаходиться текстове поле введення і кнопка, а в іншому – ще одна кнопка. Зараз ви дізнаєтеся, як можна стилізувати ці елементи

Поверніться до параметрів стилів, поставте кому після блоку container і додайте нижче нові стилі:

flowRight: {
flexDirection: 'рядок',
alignItems: 'center',
alignSelf: 'stretch'
},
buttonText: {
fontSize: 18,
color: 'white',
alignSelf: 'center'
},
button: {
height: 36,
flex: 1,
flexDirection: 'рядок',
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 source={require('./Resources/house.png')} style={styles.image}/>

Тепер додайте відповідний стиль зображення в блоці зі стилями, не забувши поставити кому після попереднього селектора:

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
style={styles.searchInput}
value={this.state.searchString}
placeholder='Search via name or postcode'/>

Ми виставили значення властивості TextInput – тобто показується користувачеві тексту – на поточне значення змінної стану searchString. Отже, ми подбали про початковому стані. Але що буде, коли користувач відредагує цей текст?

Перш за все, створимо метод, який виступає в ролі обробника подій. Перейдіть до класу SearchPage і додайте даний метод відразу після constructor:

onSearchTextChanged(event) {
console.log('onSearchTextChanged');
this.setState({ searchString: event.nativeEvent.text });
console.log(this.state.searchString);
}

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

Щоб даний метод викликався кожен раз при зміні тексту, повернемося до поля TextInput методи render і додамо властивість onChange. У підсумку тег прийме наступний вигляд:

<TextInput
style={styles.searchInput}
value={this.state.searchString}
onChange={this.onSearchTextChanged.bind(this)}
placeholder='Search via name or postcode'/>

Коли користувач змінює текст, викликається функція, яка додається до властивості 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 ?
( <ActivityIndicatorIOS
size='large'/> ) :
( <View/>);

Це тернарний оператор 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 і додайте в нього наступний рядок:

<Text style={styles.description}>{this.state.message}</Text>

Вона відповідає за відображення внизу екрану повідомлень для користувача.

Знайдіть клас 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 повертають список локацій, підібраних за принципом найкращого вибору. Чому б не скористатися цією опцією і не надати користувачам кілька додаткових пропозицій?

Збережіть зміни, поверніться в симулятор і натисніть Cmd+R. Спробуйте ввести пошуковий запит 'london'. Ви повинні побачити в балці повідомлення про те, що було знайдено 20 об'єктів нерухомості (стандартне для результату кількість). А тепер спробуйте ввести невірний запит, наприклад 'narnia'. Ви побачите таке повідомлення:



Час попрацювати над виведенням результату пошуку на екран.

Виводимо результат пошуку

Створіть новий файл під назвою SearchResults.js і додайте в нього наступне:

'use strict';

var React = require('react-native');
var {
StyleSheet,
Image,
View,
TouchableHighlight,
ListView,
Text,
Component
} = React;

Все вірно, це вираз require, що включає модуль react-native і деструктуруючий присвоювання.

Потім додайте сам компонент:

class SearchResults extends Component {

constructor(props) {
super(props);
var dataSource = new ListView.DataSource(
{rowHasChanged: (r1, r2) => r1.guid !== r2.guid});
this.state = {
dataSource: dataSource.cloneWithRows(this.props.listings)
};
}

renderRow(rowData, sectionID, rowID) {
return (
<TouchableHighlight
underlayColor='#dddddd'>
<View>
<Text>{rowData.title}</Text>
</View>
</TouchableHighlight>
);
}

render() {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow.bind(this)}/>
);
}

}

Код вище використовує спеціальний компонент – ListView, який відображає дані всередині контейнера прокручування в кілька рядів, майже як у UITableView. Дані потрапляють у ListView через ListView.DataSource і функцію, яка передає UI для кожного ряду.

Створюючи джерело даних, ви надаєте функцію, яка перевіряє ідентичність пару рядів. ListView потім використовує результат під час процесу узгодження, щоб виявити зміни в даних списку. У нашому прикладі Nestoria API повертає об'єкти нерухомості з властивістю guid, що добре підходить для наших цілей.

Додайте в кінець файлу експорт модуля:

module.exports = SearchResults;

А цей рядок потрібно вставити в початок файлу SearchPage.js, нижче виклику require для React:

var SearchResults = require('./SearchResults');

Це дозволить нам використовувати клас SearchResults зсередини класу SearchPage:

Змініть метод _handleResponse, замінивши вираз console.log:

this.props.navigator.push({
title: 'Results',
component: SearchResults,
passProps: {listings: response.listings}
});

Цей код переходить до компоненту SearchResults і передає об'єкти нерухомості з API-запиту. Використання push-методу забезпечує завантаження результатів пошуку в стек переходів, внаслідок чого у вас з'явиться кнопка 'Back', щоб повернутися в кореневий каталог.

Збережіть зміни, поверніться в симулятор, натисніть Cmd+R і спробуйте виконати пошук. Ви побачите список об'єктів нерухомості:



Нарешті ми отримали цей список. Правда, виглядає він поки нуднувато. Давайте трохи перетворимо його.

Застосовуємо стилізацію

Поступово код React Native починає набувати знайомий вам вигляд, тому ми трохи прискоримо темп роботи.

Додамо дане визначення стилю відразу після деструктурирующего присвоювання у файлі SearchResults.js:

var styles = StyleSheet.create({
thumb: {
width: 80,
height: 80,
marginRight: 10
},
textContainer: {
flex: 1
},
separator: {
height: 1,
backgroundColor: '#dddddd'
},
price: {
fontSize: 25,
fontWeight: 'bold',
color: '#48BBEC'
},
title: {
fontSize: 20,
color: '#656565'
},
rowContainer: {
flexDirection: 'рядок',
padding: 10
}
});

Тут містяться всі стилі для відображення кожного ряду даних.

Замінимо renderRow() наступним кодом:

renderRow(rowData, sectionID, rowID) {
var price = rowData.price_formatted.split(' ')[0];

return (
<TouchableHighlight onPress={() => this.rowPressed(rowData.guid)}
underlayColor='#dddddd'>
<View>
<View style={styles.rowContainer}>
<Image style={styles.thumb} source={{ uri: rowData.img_url }} />
<View style={styles.textContainer}>
<Text style={styles.price}>£{price}</Text>
<Text style={styles.title}
numberOfLines={1}>{rowData.title}</Text>
</View>
</View>
<View style={styles.separator}/>
</View>
</TouchableHighlight>
);
}

Цей код обробляє дані цін, отриманих у форматі '300,000 GBP' і забирає звідти GBP. Потім він відображає рядок інтерфейсу, використовуючи підхід, з яким ви, швидше за все, вже добре знайомі. Зображення (Image) завантажується з повернутого URL (rowData.img_url), який React Native бере з основного потоку, і додається в ряд.

Також зверніть увагу на використання стрілочної функції у властивості onPress компонента TouchableHighlight. З її допомогою захоплюється guid для ряду.

Останній крок – додати цей метод в клас, щоб керувати натисненням на екран:

rowPressed(propertyGuid) {
var property = this.props.listings.filter(prop => prop.guid === propertyGuid)[0];
}

Цей метод визначає, який об'єкт нерухомості був обраний користувачем. Правда, зараз він не працює, але ми це швидко виправимо. А поки милуйтеся результатом.

Збережіть зміни, поверніться в симулятор і натисніть Cmd+R, щоб побачити оновлений список:



Виглядає відмінно… якщо закрити очі на ціни.

Нарешті, настав час додати останню сторінку.

Додаємо сторінку перегляду інформації про нерухомості

Створіть у проекті новий файл під назвою PropertyView.js і введіть туди цей код:

'use strict';

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

Впевнений, що ви вже здатні набрати його навіть із закритими очима.

Тепер додамо стилі:

var styles = StyleSheet.create({
container: {
marginTop: 65
},
heading: {
backgroundColor: '#F8F8F8',
},
separator: {
height: 1,
backgroundColor: '#DDDDDD'
},
image: {
width: 400,
height: 300
},
price: {
fontSize: 25,
fontWeight: 'bold',
margin: 5,
color: '#48BBEC'
},
title: {
fontSize: 20,
margin: 5,
color: '#656565'
},
description: {
fontSize: 18,
margin: 5,
color: '#656565'
}
});

І сам компонент:

class PropertyView extends Component {

render() {
var property = this.props.property;
var stats = property.bedroom_number + ' bed ' + property.property_type;
if (property.bathroom_number) {
stats += ', ' + property.bathroom_number + '' + (property.bathroom_number > 1
? 'кімнат' : 'bathroom');
}

var price = property.price_formatted.split(' ')[0];

return (
<View style={styles.container}>
<Image style={styles.image}
source={{uri: property.img_url}} />
<View style={styles.heading}>
<Text style={styles.price}>£{price}</Text>
<Text style={styles.title}>{property.title}</Text>
<View style={styles.separator}/>
</View>
<Text style={styles.description}>{stats}</Text>
<Text style={styles.description}>{property.summary}</Text>
</View>
);
}
}

Часто трапляється так, що API повертає дані низької якості і з пропущеними полями. Тому перша частина render() проводить обробку даних для часткового поліпшення їх якості.

Інша його частина досить очевидна: це функція незмінного стану даного компонента.

Тепер додамо експорт в кінець файлу:

module.exports = PropertyView;

Поверніться до SearchResults.js і додайте вираз require в початок файлу, відразу після рядка React require:

var PropertyView = require('./PropertyView');

Оновіть rowPressed(), щоб переміщатися по PropertyView:

rowPressed(propertyGuid) {
var property = this.props.listings.filter(prop => prop.guid === propertyGuid)[0];

this.props.navigator.push({
title: "Property",
component: PropertyView,
passProps: {property: property}
});
}

Порядок дій вам знайомий: зберігаємо, повертаємося в симулятор і натискаємо Cmd+R. Тепер можна виконати пошук, вибрати будь-який об'єкт і перейти до перегляду інформації про нього:



Ех, ось що я називаю доступним житлом!

Додаток майже готове, залишилося тільки додати опцію пошуку по геолокації.

Реалізуємо пошук по місцю розташування

Відкрийте в Xcode файл Info.plist і додайте навпроти NSLocationWhenInUseUsageDescription наступне значення:

PropertyFinder would like to use your location to find nearby properties – 

Ось як буде виглядати ваш plist файл після додавання нового значення:



Це інформація, яку додаток буде відображати користувачам при спробі запросити їх розташування.

Відкрийте SearchPage.js перейдіть до компоненту TouchableHighlight, що відповідає за відображення кнопки 'Location', і додайте це значення:

onPress={this.onLocationPressed.bind(this)}

По натисненню кнопки викликається метод onLocationPressed (зараз ми його додамо).

Вставити всередину класу SearchPage наступний код:

onLocationPressed() {
navigator.geolocation.getCurrentPosition(
location => {
var search = location.coords.latitude + ',' + location.coords.longitude;
this.setState({ searchString: search });
var query = urlForQueryAndPage('centre_point', search, 1);
this._executeQuery(query);
},
error => {
this.setState({
message: 'There was a problem with obtaining your location:' + error
});
});
}

Дані про поточне місцезнаходження беруться з допомогою navigator.geolocation. Це інтерфейс Web API, тому він повинен бути зрозумілий кожному, хто мав справу з браузерних сервісами визначення геолокації. React Native надає власну реалізацію даного API, використовуючи нативні кошти iOS.

У разі успішного визначення поточного місця розташування буде викликана перша стрілкова функція. Вона відправить запит до Nestoria. Якщо ж щось піде не так, з'явиться стандартне повідомлення.

Так як ми редагували plist, потрібно перезапустити програму, щоб побачити зміни. На цей раз, на жаль, без Cmd+R. Зупиніть додаток в Xcode і виконайте його повторне складання. Потім запустіть проект.

Перш ніж використовувати пошук за геолокаціі, варто переконатися, що база даних Nestoria містить інформацію про вашому регіоні. Виберіть у меню симулятора Debug\Location\Custom Location ... і введіть широту з довготою: приміром, 55.02 і -1.42 відповідно. Це координати одного мальовничого містечка на узбережжі північної частини Англії, звідки я родом.

Тепер натисніть кнопку Location, дозвольте додатком визначати місце розташування і дивіться результат.



Примітка від Рея: Пошук по місцю розташування спрацював не у всіх. Як правило, виникала помилка доступу, незважаючи на те, що визначення місця було дозволено. Ми поки не до кінця розібралися з цією ситуацією. Можливо, це проблема самого React Native. Якщо кому-небудь вдалося виправити цю помилку, будь ласка, напишіть нам.

Звичайно, це не Лондон, а ціни набагато приємніші.

Що далі?

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

Якщо ви веб-розробник, тепер вам має бути зрозуміліше, можна без зусиль створити повністю нативний інтерфейс і навігацію додатки, використовуючи JavaScript і React. Якщо ви розробник рідних додатків, сподіваюся, ви по гідності оцінили такі переваги React Native, як швидке взаємодія з додатком, сучасний JavaScript і зрозумілі правила стилізації в дусі CSS.

Можливо, ваше наступне додаток буде написано на цьому фреймворку? А може бути, ви все одно продовжуєте працювати зі Swift або Objective-C? Так чи інакше, сподіваюся, вам вдалося знайти в цій статті щось нове і корисне для майбутніх проектів.

Якщо ж у вас є питання або коментарі з приводу даного уроку, приєднуйтесь до обговорення.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.