Меня зовут Алексей Андросов, я уже много лет работаю в Яндексе фронтенд-разработчиком. Два года назад мне и моим партнерам пришла в голову идея создать мобильную социальную сеть Verb. Идея Verb в том, что пользователи могут делиться небольшими статусами — “вербами” — о том, чем они занимаются в данную минуту, простыми вещами, о которых хочется рассказать друзьям, но некуда об этом написать.
И мы уже даже получили инвестиции, но сейчас не об этом. Сегодня я хочу рассказать о том, как и почему я написал мобильное приложение на react-native.
У меня есть бэкграунд бекенд-разработчика, более 10 лет опыта веб-разработки, но создание мобильного приложения для меня было абсолютно новой областью. Что было делать в такой ситуации? Быстро изучить Objective-C или Swift для iOS и Java для Android? Параллельно писать два приложения сразу показалось плохой идеей, я один явно не смог бы быстро реализовывать все идеи на двух платформах. Поэтому я начал искать варианты для кроссплатформенной разработки.
Как раз кстати пришлась шумиха вокруг React, появился react-native. Тогда мне и захотелось его попробовать. Оглядываясь назад, выбор на react-native пал, по большому счету, не из-за каких-то объективных причин, а скорее из-за субъективных:
до него уже были PhoneGap (WebView на JavaScript, CSS, HTML), Xamarin (C#) и NativeScript (JavaScript, TypeScript, Angular).
Опыт работы с PhoneGap у меня уже был и использовать его не хотелось (уже очень подкупали нативные компоненты вместо их реализации на веб-технологиях), C# я не знал, писать на Angular не хотелось, так что выбор пал на RN.
Как и NativeScript, RN имеет биндинги нативных компонент в JS, схожую систему работы (об этом поговорим позже) и возможности.
Первой версией, с которой мы начали, стала 0.7.1 (текущая версия — 0.46), первый мой коммит 21 июля 2015. Поддержки Android тогда не было совсем. Она появилась только в сентябре 2015 (0.11). При этом это была “первоначальная поддержка” платформы с кучей проблем и недоработок. Пройдет еще много времени до того, как поддержка iOS и Android станет более-менее равноценной в react-native.
Забегая вперед, скажу, что Android-версию мы в какой-то момент все же решили писать на Java. Приложение уже было нужно, а RN в тот момент оказался не готов (на текущий момент такой проблемы уже нет). А вот iOS-версия Verb полностью написана на react-native.
Что такое react-native?
Итак, react-native – это фреймфорк для разработки на react кроссплатформенных приложений для iOS и Android. React здесь используется именно как идея, а не реализация: JSX, компонентный подход, state, props, жизненный цикл компонентов, CSS-in-JS.
Если проводить аналогию с обычной веб-разработкой, то тут:
- нет HTML, но есть нативные компоненты в JSX (<View/>, <Text/>, <Image/>)
- нет CSS. Все стили пишутся CSS-in-JS, layout строится на полифиле для flexbox (при этом он немного отличается от стандарта, например, flex-direction по-умолчанию column, а не row).
- нет привычного DOM API. Никаких window, document и всего подобного. Из привычных API только GeoLocation API (navigator.geolocation) и XHR
Верстка компонент выглядит примерно вот так:
render() {
return (
<View style={ styles.container }>
<TouchableOpacity onPress={ this.props.onPress }>
<Image source={ require('../icons/close.png') } resizeMode="cover"/>
</TouchableOpacity>
<Text style={styles.placeText} numberOfLines={1}>
{ myText.join(', ') }
</Text>
<ScrollView
keyboardDismissMode="on-drag"
refreshControl={<RefreshControl/>}
/>
{this.props.children}
</ScrollView>
</View>
);
}
Чтобы было проще понять, приведу аналогию с привычным HTML. <View/> — это <div/>, <Touchable*/> — <a/> (их несколько, с разными реакциями на нажатия), <Image/> — <img/>, <ScrollView/> — что-то наподобие <div style=”overflow: scroll”/>
CSS выглядит более привычным:
const styles = StyleSheet.create({
default: {
fontSize: PixelRatio.getPixelSizeForLayoutSize(7),
color: 'rgba(0, 0, 0, 0.60)'
},
suggestUser: {
height: PixelRatio.getPixelSizeForLayoutSize(100),
backgroundColor: '#FFF',
shadowColor: '#000',
shadowOffset: {
height: -5
},
shadowRadius: 5,
shadowOpacity: 0.5
}
};
StyleSheet объявляет и компилирует стили, они сразу напрямую передаются в native, чтобы не гонять каждый раз.
PixelRatio нужен для пропорционального увеличения размера элементов в зависимости от экрана.
Как работает RN
Если прекрасная статья от Tadeu Zagallo про кишки RN, я вам вкратце расскажу ее суть.
В iOS есть три треда:
— shadow queue — очередь обработки и отрисовки layout
— main thread — тут работают компоненты
— JavaScript thread — тут работает JS
Общая схема работы выглядит так: исполняется JS (по сути React), результат работы надо передать в нативный код. Для этого есть специальный мост, который с помощью JSON передает набор инструкций. RN их исполняет и отрисовывает нативные компоненты платформы. Результат работы через тот же мост может вернуться обратно в JS.
Все общение асинхронное. При этом самым узким местом является непосредственно мост, сериализация и десериализация данных. Отсюда интересные особенности:
- Отправлять большие пачки данных JS<->native — плохая идея, будет тормозить
- Делать большие действия на JS тоже плохая идея. Это будет лочить треды и тормозить отрисовку, никаких 60fps не получится.
- Делать частые действия (например, анимацию или контроль скролла) на JS тоже не получится, будет тормозить.
Итого, общее правило: не надо забывать, что мы работает с нативным кодом, а не JS. Все, что можно сделать native, должно быть native.
Write once, run everywhere?
Не тут-то было ). На самом деле парадигма звучит так: learn once, write everywhere.
Почему так получается? Все нативно, поэтому забудьте про полную кроссплатформенность. Платформы разные, поэтому и нативные компоненты разные. У них разная логика работы и механика взаимодействия. В основном это касается компонентов навигации и взаимодействия с пользователем. Поэтому в начале разработки имеет смысл сразу накидать болванку приложения с базовыми экранами и переходами. На этом разница заканчивается, все внутренние компоненты скорее всего окажутся одинаковыми. Удобство добавляет и сам RN, весь платформоспецифичный код можно разделять на файлы component.ios.js и component.android.js, тогда для каждой платформы будет собираться своя версия без всяких if внутри.
iOS версия Verb изнутри
Изнутри приложение написано на react-native + redux. Redux пригодился, кстати, не только, как хорошая библиотека для организации работы с состоянием приложения, но и для общения между компонентами. RN провоцирует писать pure компоненты без сайд-эффектов или использования глобальных объектов, поэтому общение компонентов через общий state является чуть ли не единственным нормальным способом.
Плагины:
- React-native-deprecated-custom-components — я использую старый Navigator, т.к. еще не переехал на новый react-navigation из-за кучи багов
- React-native-device-info — информация про UUID, версию и локаль устройства
- React-native-fbsdk — мост в FBSDK
- React-native-imagepicker — моя собственная обвязка над ActionSheetIOS, CameraRoll и ImagePickerIOS для загрузки фоток
- React-native-l20n — локализация с помощью l20n
- React-native-linear-gradient — реализация градиентов
- React-native-pagecontrol — мой биндинг в UIPageControl
- React-native-photo-view — простенькая библиотека, чтобы сделать полноэкранный просмотр фоток
- React-native-svg — биндинг в svg
- React-native-vkontakte-login — мост в VKSDK
Статистика:
- react-native-fabric — мост в Fabric и Crashlytics
- React-native-google-analytics-bridge — мост в GA
- React-native-sentry — сбор крешей и отправка в собственную инсталляцию Sentry
- React-native-bugsnag — мост в BugSnag
При сборе крешей самое главное получить stacktrace из JS. Fabic такого не умеет, в Sentry получше, самым лучшим оказался BugSnag. Но мы все равно решили переехать в Sentry, чтобы все проблемы были в одном месте.
Так же мы используем геолокацию и пуши. В react-native для этого есть все необходимое.
Важная часть — производительность. Как я уже писал, узким местом в RN является мост JS<->native, поэтому проблема уменьшения количества перерисовок становится во весь рост. Отсюда следует, что все советы и best practice от react и redux имеют очень большое значение. React.PureComponent или shouldCompomentUpdate, меньше connect-компонентов, плоские структуры данных, иммутабельность — все дает хороший прирост производительности.
Неочевидные советы
Не надо затягивать обновление версий. Раньше, когда RN выпускался раз в 2 недели и почти каждая версия имела несовместимые изменения, этот совет имел очень большое значени. За 1-2 месяца фреймворк мог измениться очень сильно, поэтому обновляться сразу на пару версию было очень сложно. Сейчас ситуация обстоит лучше, релизы раз в месяц, фреймворк уже не меняется так сильно. Но я как и раньше обновляюсь сразу, как только выходит новая версия.
Унификация react-компонент (тут понадобится помощь дизайнера) позволяет очень быстро разрабатывать новые экраны. Вообще, компонентный подход и быстрое связывание компонент — это хорошая фишка как react, так и react-native.
boilerplate для создания новых экранов в rn + redux достаточно большой. Как и в react+redux, так и rn+redux придется написать много кода для создания нового экрана: создать компонент экрана, зарегистрировать его в Navigator, написать actions и reducer для перехода на экран. Плюс к этому стандартный boilerplate для обработки данных для нового экрана.
Не надо все писать на JS. Приложение надо писать максимально близко к native, искать биндинги в нужные native-компоненты или писать их самим. Уверяю, это совсем не сложно.
Надо участвовать в сообществе. В RN достаточно багов и недоработок, но нет никаких проблем их поправить. Мейнтейнеры активно участвуют в PR. Правда, зачастую приходится исправлять баги в самом native-коде, так что Java или Objective-C придется подучить. Все мои PR приняли. Например, я исправил несколько багов в работе камеры или загрузке фотографий на сервер.
Заключение
Писать на react-native оказалось удобно, быстро и приятно. Он правда позволяет быстро создавать прототипы приложений и доводить их до релиза. Технология молодая, поэтому есть типичный проблемы early adopters (например, периодические несовместимые изменения). Но с каждый релизом RN становится все лучше и стабильнее, Facebook прилагает к этому много усилий и делает на него ставку. К текущему моменту часть приложения Facebook уже написано react-native, а поддержка Android достигла уровня iOS.
Я достаточно поработал с технологией и могу сказать, что написать не слишком большое iOS-приложения, аналогичное Twitter или Instagram, вполне реально. К сожалению, глубоко попробовать android так и не получилось, поэтому утверждать не могу, но, надеюсь, что в будущем можно будет попробовать конкурировать с нашей нативной iOS-версией.
А еще мы ищем удаленного разработчика на частичную занятость! Пишите на doochik@ya.ru.
Полезные ссылки
- Наш стартап Verb — verbapp.me
- iOS-версия Verb — itunes.apple.com/us/app/verb/id1149262483?l=ru&ls=1&mt=8
- Android-версия Verb — play.google.com/store/apps/details?id=verbapp.social
- Официальный сайт react-native — facebook.github.io/react-native
- Различные статьи и публикации — www.reactnative.com, github.com/jondot/awesome-react-native
- Поиск плагинов — js.coach/react-native
Автор: doochik