Итак, пришла пора быстро погрузиться в тему. Для усиления эффекта, использую разные техники преобразования информации в знания. В частности, представляю конспект доклада Алексея Андросова (старшего разработчика интерфейсов, Yandex).
React Native — это фреймворк для разработки кроссплатформенных приложений для iOS и Android:
- появился в начале 2015 года
- построен на базе React
- не использует WebView и HTML-технологии
- нативные компоненты имеют биндинги в JS и обернуты в React
- поддержка iOS лучше, чем Android, но динамика многообещающая
Первый ли он? Нет!
- PhoneGap — реализация нативных компонентов на веб-технологиях, запускается в WebView
- Xamarin — от разработчиков Mono для Linux, приложения написаны на C# (проект купил Microsoft)
- NativeScript — похожие идеи, что и в React Native: XML + JS + CSS
Инструменты разработчика
- Ваш любимый редактор JS
- XCode и $100 в год на Developer account, чтобы собирать и публиковать приложения
- Терпение + Android Studio + SDK + Эмулятор
React Native: взгляд сверху
- Релиз примерно раз в 2 недели
- Все прелести early adopters: несовместимые изменения, stackoverflow driven development
- Все тот же обычный React
React Native: взгляд изнутри
- нет HTML, но есть компоненты платформы в JSX
- нет CSS, но есть CSS-like полифилы
- нет DOM API. Вообще. Совсем.
- ES6/ES7 и всё, что может babel, но нет JIT (на iOS)
Write once, run everywhere? Нет! Вместо ожидаемых предположений, что один и тот же код будем использовать многократно на разных платформах. Learn once, write everywhere. Одинаковая архитектура приложения (React для построения интерфейса, Redux для круговорота данных).
Немного философии
Все нативно, поэтому забудьте про полную кроссплатформенность. Платформы разные, поэтому и компоненты разные. У них разная логика и механика взаимодействия. Можно писать все на JS и выкинуть понятие native, но вы этого не хотите. Native — это ваше преимущество!
На примере приложения Vine в iOS. Что принято делать в iOS? Внизу TabBar, в нем принято переключать экраны: главная, профиль, поиск. Сверху NavigationBar, и в нем принято писать название и кнопки слева-справа (слева обычно back стоит, а справа — какое-нибудь действие). А в Android все не так. Есть тоже NavigationBar, но он другой, в нем не принято кнопки делать. Для этого есть отдельный компонент, называется ToolBar-ом. В Android-е принято делать SegmentedActivity — она сверху, очень похожа на iOS TabBar, но механика работы у него абсолютно другая. Если в TabBar-е мы не можем свайпом переключать экраны, то в Android-е это можно делать, и это принято делать, и именно так оно и работает.
Кроссплатформенность
- интерактивные компоненты (навигация, меню, ...) будут разные
- из-за этого раскладка приложения может быть разной
- логические компоненты приложения будут одинаковые, и в это главная прелесть
Из чего состоит приложение?
- нет привычных div, span, button, input и т.п.
- нет привычного CSS
- нет DOM
Компоненты
Приложение строится из компонент платформы — это нативные модули, завернутые в React-компоненты
- есть кроссплатформенные: View, Text, Image, Picker, ...
- есть специфичные для iOS: TabBarIOS, ActionSheetIOS, ...
- есть специфичные для Android: BackAndroid, ToolbarAndroid, ...

[Вот так это выглядит, реальный код]
Интересно, что кнопок нет! Для вас любая кнопка — это просто стилизованная область, у которой есть обработчик нажатия. Никакой механики кнопки нет. И поэтому в React-е есть вот эти touchable-элементы, вы оборачиваете всё, что угодно и у вас всё что угодно становится кнопкой по сути (есть обработчик onPress). Scroll-ы — отдельный компонент. Это сегментированный вид. Он рендерит только то, что находится на экране, и с ним нужно работать чуть по другому. Потому ScrollView тут тоже отдельный. Отдельная механика, если используется клавиатура. Потому отдельное свойство есть — чего с ним делать. Отдельно свойство refreshControl. Если кто-то знает, как разрабатывать на iOS, то это очень похоже.

[Вот как выглядит текстовое поле]
Какие-то свойства совпадают с привычным HTML-input-ом, а другие — нет.
CSS не настоящий — это полифил
- верстка абсолютными значениями, никаких относительных величин
- для раскладки есть ограниченная реализация flexbox-свойств
- полной поддержки CSSx никогда не будет, она не нужна
- всего реализовано около 70-ти свойств, на самом деле их хватает; на всякий случай всегда есть SVG.

[Пример CSS #1]
Компонент PixelRatio преобразует значения из density points в настоящие пиксели для разных экранов (Retina и прочее).

[Пример CSS #2]
Вот это пример с flex-ом. Хватает минимального набора, чтобы верстать.
Болванка приложения
- подключайте redux, без него будет совсем плохо
- продумайте свои экраны и логику переходов
- будут различия для iOS и Android
- главным компонентом будет Navigator

[Пример кода приложения]
Navigator
Имеет ряд проблем:
- императивное API (методы) для управления
- надо сразу продумать взаимодействие с Navigator
- анимации и жесты сложно управляемы
- NavigatorBar совсем отвязан от общей жизни
- после разработки нескольких экранов вы начнете испытывать боль
Во многом, проблемы решаются с помощью redux.

[Почему это плохо?]
Чтобы запушить какой-нибудь роут, или сделать back (перемотать на другой экран) — надо сделать ссылку на Navigator, а потом эту ссылку получить. Причем изначально её не будет, т.к. Navigator-а еще нету.

[Интерфейс выглядит, как связанные компоненты]

[А на самом деле всё выглядит вот так]
NavigationBar или зачем нужен redux
- не стоит пытаться связывать MyComponent и NavigationBar
- лучше использовать global state и dispatch actions (flux/redux)

В декабре 2015 Eric Vicenti организовал проект navigation-rfc, с помощью сообщества, попытался решить проблемы Navigator. В феврале 2016 проект переехал в мастер React Native под название NavigationExperimental и теперь развивается силами Facebook. А старый Navigation больше не будет поддерживаться.
NavigationExperimental — что сделано
- состояние управляется через reducer
- вместо императивного API — action dispatcher
- логика навигации отделяется от представления
- разделение NavigationBar, анимаций и жестов управления на различные компоненты

[Пример кода навигации]
Анимации
- реализуются через специальный компонент Animated
- есть <Animated.View>, <Animated.Image>, <Animated.Text>
- работают вне React, напрямую обновляя нативные компоненты

[Пример кода анимации]
Работает очень плавно, можно комбинировать последовательно/параллельно, и делать довольно безумные штуки.
Нативные модули
React Native реализует основные, но не все. Если модуля нет:
- поискать компонент в UIExplorer (демо-приложение React Native)
- найти правильное название модуля в терминах OS
- поискать в исходных текстах, возможно он недокументирован (как фотку сделать, SVG)
- js.coach/react-native — плагины для React Native
- отдавайте предпочтение нативной реализации, а не JS
Как подключить нативные модули
Используйте rnpm — React Native package manager:
$ rnpm link react-native react-native-google-analytics-bridge
Кроссплатформенность компонент
Неправильный путь — разложить все по папкам:
- common/components
- android/components
- ios/components
и подключать их в зависимости от платформы
Правильный путь — разложить все по папкам:
- MyComponent/Component.ios.js, MyComponent/Component.android.js
- ComponentIOS, ComponentAndroid
Для платформо-зависимых компонент (ComponentIOS, ComponentAndroid) удобно класть рядом пустышку, и не испытывать проблем, что какой-то компонент не найден на платформе.
Как написать нативный компонент
- не надо писать все на JS
- у компонента должна быть реализация в UIKit или Android API
- если ее нет, то вы хотите странного, скорее всего
Финал доклада, видео с этого места.
PS
Коллега скинул ссылочку на Weex — две недели назад Alibaba передал проект Apache.
И опять внутри Vue. Что-то оно все время путается у меня под ногами.
Только проникся идеями React+Redux, бегаю с ними, как сумасшедший с бензопилой, в попытках везде применить. А тут что получается — разворачивай дебаркадер?!
Будет очень интересно почитать сравнение, может кто возьмется — тема на Хабре новая.
Комментарии (83)
frol
05.03.2017 09:55+1- XCode и $100 в год на Developer account, чтобы собирать и публиковать приложения
- Терпение + Android Studio + SDK + Эмулятор
Можно немного срезать угол, по крайней мере на этапе прототипирования, если использовать Exponent:
Exponent lets web developers build truly native apps that work across both iOS and Android by writing them once in just JavaScript.
It's open source and free and uses React Native.andreylat
05.03.2017 11:49+1как я понял, еxponet — это для написания собственных приложений в базовом приложении, чем-то похоже на AdobeAir. Ставишь на телефон exponet, а потом делаешь/загружаешь приложения для него.
Верно?frol
05.03.2017 11:53+1У них есть два режима и они не взаимоисключающие. Можно быстро и бесплатно получить своё приложение на телефоне запустив его через клиент, а можно собрать пакет и опубликовать в стор, тогда и появляется +$100 на iOS и +$50 на Android. Эта идея не нова, как минимум я такое видел для приложений "PhoneGap поколения", например, AppGyver.
dotneter
05.03.2017 11:50+3Недавно закончил небольшой хобби проект
Веб (react-native-web) + андроид (react-native).
http://ru.quizexer.com/
От работы с React Native остались по большей части положительные впечатление.
Из всего кросплатформенных решений (веб/мобайл) что перепробовал, этот вариант считаю наиболее удачный.
Да, есть баги и пока не хватает нативных модулей, но код получился практически идентичный,
что для хобби проекта главный плюс.
И вместо js можно использовать любой язык который в него компилируется, что тоже большой плюс.comerc
05.03.2017 11:55+1По опыту, каких компонентов не хватает? Автор доклада уверяет, что уже можно найти все, что угодно.
dotneter
05.03.2017 12:01+2Даже для такого простого приложия(хотя немного специфичного),
пришлось написать два нативных модуля.
1) для работы c SoundPool (Есть react-native-sound но это не совсем то и он немного глючный, крашил приложение на ряде устройств)
2) для работы с Google Play Leaderboards.
VolCh
05.03.2017 11:57+1Выбор Redux для хранения глобального состояния чем обусловлен? Альтернативы рассматривались?
comerc
05.03.2017 12:00Redux — это архитектура приложения. Хотя камрады на Clojure со мной не согласны. У них свои велосипеды.
VolCh
06.03.2017 14:20Насколько мне известно, Redux — библиотека (http://redux.js.org/ — Redux is a predictable state container for JavaScript apps. ), реализующая подмножество Flux архитектуры.
comerc
06.03.2017 14:46Из подмножества Flux, для меня очевидный выбор — Redux. Просто в силу его популярности, отсюда все вытекающие: документация, поддержка, расширения, вакансии, резюме. Так-то я уже наигрался с никому не нужными велосипедами.
frol
NavigationExperimental уже эволюционировал в https://reactnavigation.org/ "Learn once, navigate anywhere". Я не знаю всей истории, но похоже, что это будет рекомендованным решением (сейчас он на этапе беты).
EaE
Собственно «вся история» старого навигатора заключается в том, что Navigator, ключевой компонент любого RN-приложения… не подчиняется React-логике! Когда в корневом элементе меняешь state, и Navigator получает таким образом новые проперти, он чхать на это хотел, а все переходы выполняются императивно, старыми-добрыми push() и pop(). С redux соответственно он тоже нифига не дружит.
И об этом даже не написано в документации, хотя как раз об этом-то стоило упомянуть, ну не знаю, ЖИРНЫМ КРАСНЫМ МИГАЮЩИМ ЗАГОЛОВКОМ в самом верху описания компоненты.
Разработчики RN на все зарепорченные на эту тему баги вяло отмахиваются, что решение это из перформанс соображений, а на просьбы хотя бы добавить информацию об этом в документацию отвечают почему-то «вам тут не стековерфлоу».
Чем NavigatorExperimental по сути отличается от Navigator, в документации тоже не описано (спасибо автору статьи, теперь хоть буду знать).
comerc
Буквально вчера искал, как React-приложении выполнить переход между страницами, ничего лучше, чем router.push() пока не нашел. Пример. Надеюсь, что плохо искал.
frog
Долго изучал этот вопрос, остановился на https://github.com/aksonov/react-native-router-flux
Только там Drawer необычайно тормозной, вместо него использую https://github.com/react-native-community/react-native-side-menu
comerc
Пожалуйста, можно пример, как выполнить переход между экранами?
frog
Там есть подробнейший пример:
https://github.com/aksonov/react-native-router-flux/tree/master/Example
Rag0n
В 0.43 NavigationExperimental будет deprecated.
comerc
А как бы побыстрее собирать проект на этапе разработки под Android? На IntelXDK сборка проекта PhoneGap выполнялась шустро, если не путаю. Судя по видео в конце доклада, для Mac-а тоже Simulator готов к изменениям кода практически в реальном времени. А в Android Studio наблюдаю задержку 20 сек.