Как я написал мобильное приложение на react-native

в 14:07, , рубрики: javascript, React, я пиарюсь

Меня зовут Алексей Андросов, я уже много лет работаю в Яндексе фронтенд-разработчиком. Два года назад мне и моим партнерам пришла в голову идея создать мобильную социальную сеть 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.

image

Все общение асинхронное. При этом самым узким местом является непосредственно мост, сериализация и десериализация данных. Отсюда интересные особенности:

  • Отправлять большие пачки данных 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.

Полезные ссылки

Автор: doochik

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js