В этом уроке мы будем изучать 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                                  ¦
 ¦                                                                            ¦
 L-----------------------------------------------
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: '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 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: 'row',
    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
        ? 'bathrooms' : '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? Так или иначе, надеюсь, вам удалось найти в этой статье что-нибудь новое и полезное для будущих проектов.

Если же у вас есть вопросы или комментарии по поводу данного урока, присоединяйтесь к обсуждению.
Поделиться с друзьями
-->

Комментарии (47)


  1. cawo4ok
    21.06.2016 18:51
    +3

    Статья супер! Разжевано все до мелочей… Ждем следующий урок! Уже знаю чем я буду заниматься сегодня :)


  1. Bublik
    21.06.2016 19:03
    +3

    Redux нужен. Без него на RN писать очень тяжко.


  1. sradyukov
    21.06.2016 23:02

    Redux или MobX


  1. Bimawa
    21.06.2016 23:03

    Да про Redux там переведите если есть? плз!


    1. NeXTs_od
      22.06.2016 13:25

      тяжело ваш слог читается


  1. ByRon
    21.06.2016 23:03
    +1

    title: {
    fontSize: 20,
    color: '#656565'
    },

    20 это чего? em, rem, px, pt и остальных вариаций размеров в CSS, так-же как и width/height. width: 80 — восемдесят кого? процентов? ну ок, если процентов, от родителя.
    Но фонтСайз: 20% получится от чего? какой по дефолту фонт сайз, что за цифры? это 20 сантиметров или 20 километров?

    Просто мне не понятно в чем он FZ считает, интуитивно догадываюсь что в пикселях — но хотелось-бы узнать точнее.


    1. IvanPanfilov
      22.06.2016 07:07
      -2

      этож React Native — ты просто подставляеш цифры а оно само думает как отобразить. т.е. не важно в чем измерять.


    1. i_surin
      22.06.2016 10:27

      ну браузер по дефолту это понимает как пиксели, если в css писать отступ или размеры шрифтов в таком виде, то он поймёт это как px, скорей всего в RN тоже самое


    1. Murmurianez
      22.06.2016 13:44

      Думаю, по аналогии с NativeScript — points
      www.paintcodeapp.com/news/ultimate-guide-to-iphone-resolutions


  1. boolive
    21.06.2016 23:52

    Собранное приложение в итоге из чего состоит? Какие-то эмуляторы js, сервера..?


    1. Fedcomp
      22.06.2016 07:24

      зачем пихать эмулятор если можно запихнуть движок?


    1. IvanPanfilov
      22.06.2016 09:01

      движок js встраивается определенно — там js не компилится в нативный код.
      так что подозреваю что такая же куча говна из рантайма и набора скриптов.

      единственное отличие — нативные контролы вместо html.


      1. Fedcomp
        22.06.2016 10:11

        Единственное отличие «нативного» приложения от react-native'ного это «нативный» код. Кстати не факт что java машина например будет на порядки лучший результат показывать чем v8 портированный на андройд.

        С тем же успехом можно node.js назвать «куча говна из рантайма и набора скриптов», но, боюсь вас не поймут.


        1. boolive
          22.06.2016 12:17

          А это как понимать?

          Когда приложение запускается, RCTRootView загружает приложение из этого URL:

          http://localhost:8081/index.ios.bundle


          1. Fedcomp
            22.06.2016 19:10
            +1

            Понимайте это так что dev server который стоит на вашем компьютере, компилирует бандл при каждом редактировании файлов и отдает эмулятору/телефону. В скомпилированном приложении этот бандл поставляется вместе.


        1. IvanPanfilov
          24.06.2016 07:54

          > С тем же успехом можно node.js назвать «куча говна из рантайма и набора скриптов»

          а разве это не так?


          1. Fedcomp
            28.06.2016 08:26

            Для вас кошерны только системы написанные на C, скомпилированные и с максимальной производительностью? наверное по 10 лет на разработку простого проекта тратите.


  1. alexhouse
    22.06.2016 04:36
    -1

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


    1. Slowz
      22.06.2016 07:07
      +2

      На самом деле скорость работы приложений на RN разительно отличается от приложений построенных с помощью phonegap/cordova, приближаясь по быстродействию к нативным. Собственно в этом и заключается прелесть данной технологии — использовать для написания мобильных приложений технологии из веба и на выходе получать качественное приложение. Плюсом также можно отнести обилие технологий, которые можно использовать. Redux, reflux, mobx и т.п. для контроля состояний. Можно использовать другие языки, например TypeScript, ClojureScript, CoffeScript если JS не удовлетворяет потребностям.
      В целом мне нравится ReactNative и то как он развивается. Уже и microsoft взялись за его поддержку https://github.com/ReactWindows/react-native-windows.


    1. Fedcomp
      22.06.2016 10:18
      +1

      DOM очень медленный. В react-native юзается виртуальный дом который избавлен от практически всего, соответственно многократно быстрее чем обычное html приложение.


    1. i_surin
      22.06.2016 10:31

      поправьте если ошибаюсь, но насколько знаю, тот же facebook, instagram, 2gis и meduza.io в своих мобильных приложениях как раз react используют, жалоб на скорость работы вроде как нет, разве что загружаются чуть дольше обычных


    1. mrTyler
      22.06.2016 11:10
      +1

      у меня есть опыт разработки на phonegap и react-native и знаете — вы ошибаетесь. Вам нужно обязательно попробовать react-native и вы поймете, что скорость там отличная. Посмотрите в showcase на сайте react-native и попробуйте.


      1. ivann
        22.06.2016 11:55

        Согласен. По наблюдениям на PhoneGap основные тормоза не от JavaScript, а от медленной отрисовки браузером. В профайлере практически не видно js, одна отрисовка. Тормоза от JavaScript есть только на старте, если js очень много. У нас его уже несколько мегабайт и на Андроид его загрузка занимает несколько секунд.


  1. Balintrue
    22.06.2016 10:27

    Спасибо, отличная статья! С нетерпением жду продолжения с описанием процесса для Android-приложения.


  1. mmxdesign
    22.06.2016 10:27

    Спасибо очень интересная статья, как раз давно уже хотел попробовать React и React Native.
    Дефолтное приложение запустилось но когда все удалил из index.ios.js и заменил вашем текстом для HelloWorld,
    начал ругаться что «Super expression must either be null or a function, not undefined».
    Видать бабел с реактом ругаются на что то… не могу понять…
    i.imgur.com/Xa36TNW.png?1


  1. ArtificialPasha
    22.06.2016 10:27
    +1

    >>Я использую Sublime Text, это недорогой и очень удобный инструмент.
    >>Но Atom, Brackets или любой другой легковесный редактор тоже отлично подойдет.
    А как с поддержкой тяжеловесных IDE и дебаггеров?


    1. mrTyler
      22.06.2016 13:50
      +1

      webstorm отлично справляется с этой задачей


    1. Yozi
      22.06.2016 16:31

      WebStorm почти хорошо, но мне не хватает полной поддержки Flow в нём, у React-Native пока сложно с документацией и Flow (работающий хорошо с Atom) очень выручает


  1. john_samilin
    22.06.2016 10:53

    Я прочитал статью, прочитал комментарии к оригинальной статье и даже задал свой, но так и не получил ответа на один вопрос. Может, здесь есть знатоки и они ответят мне на него. Вопрос следующий:

    Что значит «UI приложения выражается как функция текущего состояния приложения»?

    У вас не возникло такого вопроса при переводе?
    Любой UI — это функция текущего состояния, разве нет? Вы вводите текст — внешний вид приложения изменяется. Нажимаете кнопку — внешний вид изменяется. Или это что-то из области философии? Если так, то получается какой-то безаппеляционный аргумент против Титаниума.


    1. ivann
      22.06.2016 11:58

      Любой UI — это функция текущего состояния, разве нет?


      С большой натяжкой. В реакте это более строго. Любой реакт компонент содержит два метода: setState и render. Сам компонент и является той функцией состояния.

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


      1. john_samilin
        22.06.2016 12:15

        А почему это преимущество? В чем его эффективность?


        1. ivann
          22.06.2016 12:53
          +1

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


        1. Murmurianez
          22.06.2016 13:45

          Вам не нужно следить за состоянием UI — он всегда рисуется заново на основе текущих данных (не нужно удалять/изменять отработавшие компоненты — просто меняете данные). Из этого вытекает чистота и простота кода.
          Плюс, рендер React на самом деле не обновляет UI полностью (хотя с точки зрения разработки это выглядит именно так), а делает атомарные изменения, что положительно влияет на производительность.


  1. savostin
    22.06.2016 13:44

    Эм, а стили в коде одному мне мешают?
    Нельзя ли как-то по-старинке, отделить вид от поведения?


    1. NeonXP
      22.06.2016 14:18

      Можно! Лично я предпочитаю делать следующую структуру:

      Styles.base.js — в нем объект в котором стили общие для обеих платформ, вида

      export default {
          container : {
              flex: 1
          }
      };
      


      Styles.ios.js — стили специфичные для iOs:

      import BaseStyles from './Styles.base';
      import { StyleSheet } from 'react-native';
      
      export default StyleSheet.create([ BaseStyles, {
      ...
      } ]);
      


      Styles.android.js — специфичные для Андроида стили, содержимое аналогично предыдущему

      Затем в коде нужного компонента уже делаю что то типа того:

      import styles from './Styles'; //При этом подключатся базовые + специфичные только для текущей платформы
      
      ....
      
      <View style={styles.container}>
      ...
      </View>
      


      Как-то так. Писал из головы, возможны неточности в деталях.


      1. Houston
        22.06.2016 21:01

        Можно. Всё можно, это ж JavaScript: ) Можно даже БЕМ-подобную структуру использовать:


        app/
          components/
            /some-component/
              some-component.js
              some-component.style.js
              some-component.test.js


  1. Starche
    22.06.2016 13:49

    Круто, спасибо, как раз хотел в тему вкопаться. Простите, статью пока до конца не прочитал — это надо взять редактор, взять устройство, и тогда уже действовать. Хотелось бы задать вопросы, на которые наверняка можно найти ответы в гугле, но может кто-то может ответить быстро на своём опыте — это всегда дороже
    1) Можно ли это в продакшн. Версия реактнейтив ещё не добралась до первой, значит ли это что всё нестабильно, или что много важных фич не хватает?
    2) Есть ли у кого опыт кроссплатформенного приложения написаного на React из-под windows? Насколько я понимаю, собирать iOS приложение можно только под макосью, теоретически для этого можно и виртуалкой воспользоваться. Может кто-нибудь уже занимался подобными извращениями?


    1. NeonXP
      22.06.2016 14:25

      1. В принципе, можно. Facebook вполне использует, а им виднее готово их поделие или нет. Насчет версии, скорее то что, API еще может меняться (и менялось сильно в 0.18 вроде версии). Каких-то фич и правда не хватает (например, RCTMaps для Android), но это вполне обходимо стороними компонентами. Так что, бояться нечего кроме смены API за которым надо следить. Ну или не обновляться.


  1. Houston
    22.06.2016 21:03

    var React = require('react-native');


    Такое не работает то ли с 0.25 версии, то ли с 0.26. Теперь нужно React импортировать из пакета "react": var React = require('react');. Вот так и работаем, при каждом обновлении react-native приходится что-то переписывать…


  1. Tsimur_S
    24.06.2016 16:20
    +1

    Не очень понимаю что имеется в виду под понятием нативные компоненты. В чем разница с Titanium Appcelerator? Допустим мы хотим создать приложение под андроид со списком — js будет транслирован в байт код далвика/art вызывающий внутри активитити условно вида new LinearLayout( ).addView(new ListView())? Тогда сразу возникает миллион вопросов: как нам тут помогает virtual dom и вообще зачем он тут? Зачем нам вообще js движок на девайсе если мы можем транслировать все во время генерации apk?


    1. ivann
      27.06.2016 18:22

      JavaScript код остается в текстовом виде. JS один из языков, для которых JIT компилятор эффективнее, чем AOT-компиляция. JIT-компилятор видит как исполняется блок кода и может перекомпилировать его "на лету" в случае необходимости. Написать оптимизирующий AOT-компилятор JS крайне затруднительно из-за самого языка программирования.
      Virtual DOM нужен для того, чтобы код шарился бежду браузером и нативом. Т.е. у вас остается практически тот же код в браузере и на устройстве.


      1. john_samilin
        27.06.2016 18:31

        Насколько я понимаю, вопрос про то, как соотносятся нативное Android/iOS приложение и понятие Shadow DOM (да и вообще, применимо ли к ним DOM)


      1. Yozi
        27.06.2016 22:09

        Несколько разочарую Вас: «On iOS JSC doesn't use JIT due to the absence of writable executable memory in iOS apps.»

        пруф: https://facebook.github.io/react-native/docs/javascript-environment.html


        1. ivann
          28.06.2016 10:20

          Доки по JavaScriptCore говорят об обратном: http://trac.webkit.org/wiki/JavaScriptCore


          Предполагаю, что дока по React Native устарела. Действительно, до iOS8 JIT был доступен только для Safari. Однако с релизом iOS 8 компилятором обзавелся и WKWebView: http://developer.telerik.com/featured/why-ios-8s-wkwebview-is-a-big-deal-for-hybrid-development/
          Предполагаю, что и JavaScriptCore мог им обзовестись.


          1. Yozi
            28.06.2016 11:35

            Действительно. Хорошие новости.


    1. Yozi
      27.06.2016 22:06

      Кросс-компиляции там нет, выполняется именно javascript, который использует нативные компоненты через предоставляемое ими API.

      Virtual DOM позволяет уменьшить количество взаимодействий с API для отрисовки точно так же как и «обычный» React в браузере — дорогостоящее обращение к API идёт только для перерисовки изменённых участков отображения, а не всего шаблона.

      Для меня достоинством React Native является так же их «Learn once, write anywhere», Objective-C мне не знаком, а вот React Native не сильно отличается от его браузерного собрата


  1. DarwinTenk
    01.07.2016 15:02

    Вопрос по теме. Делал как здесь описано, но в момент первого подключения fetch возвращает мне TypeError: Network request failed.

    Погуглив я нашел вариант, что это из-за localhost но не понятно шде и на что менять.