Привет! Я Илья, фронтенд-разработчик. В ЮMoney работаю четыре года. Занимался личным кабинетом интернет-магазинов на B2B-продукте ЮKassa. Последний год развиваю продукт для расчетно-кассового обслуживания ЮBusiness.

Что значит РКО

Юридическому лицу нельзя просто открыть счет в банке: нужен специальный счет. Закон регулирует денежный оборот компаний, поэтому созданы специальные системы для движения денег юрлиц. Одна из них — наш сервис ЮBusiness.

Есть веб-версия, есть приложения в App Store и Google Play.

Веб-версия ЮBusiness
Веб-версия ЮBusiness
Мобильное приложение ЮBusiness
Мобильное приложение ЮBusiness

Я приоткрою мир разработки на React Native, а тем, кто с ним знаком, расскажу о встреченных болях. Статья поможет вам, если вы смотрите на этот фреймворк и если вы уже начали с ним работать.

В начале 2020 года в ЮMoney появилось новое направление работы: мы решили создать продукт для расчетно-кассового обслуживания. Одним из требований бизнеса было запустить мобильное приложение. Это самый удобный путь обслуживания счетов.

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

Почему именно React Native?

У продакта был удачный опыт с React Native, его не нужно было обосновывать. С языком Dart для Flutter ни у кого опыта не было. Cordova и другие варианты не нативные, это WebView.

Как вы понимаете, мы выбрали RN, потому что:

  • У нас классные мобильные разработчики. Сейчас их не хватает для всех команд, и на нашем проекте не было выделенного специалиста по мобилке.

  • Для разработчиков это новая технология, возможность внедрить ее в прод и радоваться.

  • Для продукта и продакта это возможность реализовывать фичи mobile first. Еще один плюс — простота сопровождения. Весь функционал на вебе и мобилке делают одни и те же люди. Нужно общаться с одной командой, а не синхронизировать две и более.

Начну с нашего знакомства с технологией. У нас  в компании уже был не самый удачный опыт с RN: мы пытались частично внедрить его в наше мобильное приложение. Были проблемы с инициализацией приложения. Переход на него на доли секунды фризил приложение, чтобы запустить RN. Также фронтендеру было сложно делать интеграцию своего приложения с нативным.

Тестирование

Чтобы писать код на RN, надо научиться тестировать. С юнит-тестами все довольно просто: ровно те же технологии, что и для веба. Уточнение: иногда нативные компоненты приходится мокать, потому что jest не всегда справляется.  

Разработчики React Native советуют два фреймворка для интеграционного тестирования (https://reactnative.dev/docs/testing-overview): Detox и Appium. Мы выбрали Detox, так как с ним уже успешно работал наш разработчик.

Как работает Detox?

Detox открывает симулятор, запускает в нем приложение и начинает исполнять описание действия из тестов, клики/тапы/заполнения инпутов, делает по ходу снапшоты, чтобы сверяться. Отдельное спасибо тем, кто придумал название Detox: ища информацию, я узнал много чаев для похудения.

Начало разработки

Итак, мы умеем писать тесты — пора начинать писать код. Структура приложения схожа с веб-приложением на React.

type Props = {
 label: string;
 placeholder: string;
};

const CardNumberComponent: React.FC<Props> = ({label, placeholder}) => {
 return (
  <>
   <Title size='m' text={label} />
   <Decorator indentB='xs'>
    <TextInputField
     mask='[0000] [0000] [0000] [0000000]'
     name='pan'
     hasClear
     label={placeholder}
     keyboardType='number-pad'
     testID={TEST_ID.CARD_NUMBER_INPUT}
     config={{
      parse: removeNotNumberSymbols,
      validate: validateCardNumber({
       required: VALIDATION_MESSAGE,
       format: VALIDATION_MESSAGE,
       length: VALIDATION_MESSAGE
      })
     }}
    />
   </Decorator>
  </>
 );
};

export const CardNumber = memo(CardNumberComponent);

У нас есть набор компонент, которые рендерятся в каждой платформе в UI-элементы. Логика, компоненты — все реализуется как в веб-проекте.

Нативная часть

Настал момент задачек по внедрению логики, которую уже реализовали мобильные разработчики… Мы можем переиспользовать ее!

Здесь было минимум использований: 

  • переиспользуемый модуль мобильных разработчиков с авторизацией в нашей системе; 

  • реализованный ранее модуль для работы с ThreatMetrix — сервисом, который помогает обезопасить платежи;

  • Threads-чаты.

Debug

Ошибки случаются чаще, чем хотелось бы, поэтому необходим debug-инструмент. Но сначала расскажу о разработке на симуляторах и реальных устройствах.

Для iOS симулятор требует только Xcode. Запускаем сборку на выбранный симулятор и разрабатываем. 

Инструменты для React-разработки под iOS
Инструменты для React-разработки под iOS

Для работы с реальным устройством понадобятся:

  • Xcode;

  • iPhone;

  • учетная запись разработчика в App Store, добавленная в компанию — разработчика приложения;

  • UDID iPhone, добавленный в разрешенные девайсы для тестирования приложения;

  • подключение по проводу к компьютеру для сборки приложения на iPhone.

Для Android на симуляторе требуется только Android Studio. Запускаем сборку на выбранный симулятор и… И все, разрабатываем.

Инструменты для React-разработки под Android
Инструменты для React-разработки под Android

Для работы с реальным устройством понадобится только Android Studio и устройство. Android можно собирать в дебаг-режиме на устройстве по общему Wi-Fi, не подключая смартфон к компьютеру по проводу.

У React Native есть дефолтный дебаггер. Он открывается в любом браузере и позволяет следить за консолью, профилировать приложение, использовать точки остановки — и на этом все. Этого функционала для полноценного debug катастрофически мало.

Следующий вариант — React Native Debugger.

Он позволяет запускать приложение, которое внешне напоминает режим разработчика в Chrome. Здесь можно посмотреть на Redux Store, включить отслеживание network request, посмотреть на дерево React-компонент, попрофилировать работу приложения.

Network debugging

Теперь подробнее о том, как использовать каждую из частей дебаггера.

Включаем network debugging — и все: дальше все запросы будут логироваться, их можно просматривать.

Ограничение дебага запросов
Ограничение дебага запросов

Обратите внимание на ограничения дебага запросов. На нашем опыте столкнулись с нерабочими multipart/form-data-запросами при активированном network debugging.

React devtools

React Developer Tools не отличаются от браузера. Мы можем смотреть состояние компонент, искать их в дереве. Так же работает Element Inspector, который позволяет выбрать в UI элемент и перейти к этому компоненту в React Developer Tools.

Redux devtools

Redux Debugger. Как и в предыдущих пунктах, здесь все так же, как в вебе. Выбираем нужный instance и следим за всеми action, просматриваем store.

Дебаг стилей сыроват. Мы можем только посмотреть размеры элементов.

Есть еще один дебаггер — Flipper: https://fbflipper.com. Это модульный проект от разработчиков Facebook.

Инструмент для дебаггинга Flipper
Инструмент для дебаггинга Flipper

Работа со стилями на голову выше, чем у аналогов, из коробки есть просмотр логов/сетевых запросов. Можно устанавливать модули для Redux Store и писать их самому. Мы с ним еще не подружились, но в процессе.

Проблемы и особенности

Теперь перейдем к интересным багам и особенностям. Забегу вперед и скажу, что раньше было гораздо больше багов, но их пофиксили мейнтейнеры. Первый важный момент: React Native еще даже не мажор, поэтому стабильность не на высоте. 

Начну с одной из первых задач, с которой столкнулся, придя в команду. Дизайнер нарисовал заголовки и подзаголовки разной жирности: 400, 700, 800. Я открыл симулятор на iOS, добавил стили, что может быть проще? Но! Тестировщик вернул задачу обратно, сказал, нет жирности на Android. Я открыл симулятор Android и действительно: нет жирности у заголовков. Дальше я погуглил: естественно, это известная проблема. В качестве решения нам предлагают создать много шрифтов разной жирности. https://github.com/facebook/react-native/issues/26193#issuecomment-525028689.

Шрифт на Android
Шрифт на Android

Мы поговорили с дизайнером и решили не использовать это.

В моменте в приложении на Android появилась едва заметная полосочка во время анимации нажатия.

Рамка в пиксель на Android
Рамка в пиксель на Android

Как ужасно это выглядит! Дальше повторилась ситуация из предыдущей проблемы. Гуглю, и что мы видим? Это известная проблема. https://github.com/facebook/react-native/issues/29010#issuecomment-636653305. Выходы такие: либо считаем размеры layout сами, либо используем костыльное решение: сдвиг на -1 пиксель.

KeyboardAvoidingView имеет ряд проблем с «прилипанием» элемента к клавиатуре.

Где не справляется обычный KeyboardAvoidingView:

  • на экранах iOS со сворачивающимся хедером (параметр headerLargeTitle: true);

  • на экранах, где нужен инпут, прилипающий к клавиатуре (из-за подскролла Android добавляет лишний паддинг).

Для навигации мы используем react-navigation.

Раньше была масса проблем, сейчас многие исправлены. Но есть запомнившиеся особенности и недочеты, которые остались. Например, чтобы сделать модалку (botoom sheet) без стандартной анимации экрана модалки из библиотеки навигации, придется поплясать.

Такой компонент, у которого своя анимация, я хочу открыть по тапу на кнопку.

// ModalInit.txs
import {BottomSheet} from '../components/BottomSheet';

const ModalInit = () => <BottomSheet><Text>Ю модалка</Text></BottomSheet>;

Для этого придется сделать вложенный стек. Сначала создаем стек для кастомных модальных окон. Через параметры экрана выключаем показ хедера и делаем прозрачный фон.

// ModalStack.tsx
import {createNativeStackNavigator} from 'react-native-screens/native-stack';
import {ModalInit} from './ModalInit';

const Stack = createNativeStackNavigator();

/**
 * Поднавигация экранов c BottomSheet'ами
 */
export function ModalsNavigationStack() {
 return (
  <Stack.Navigator
   initialRouteName='ModalInit'
   screenOptions={{headerShown: false, contentStyle: {backgroundColor: 'transparent'}}}
  >
   <Stack.Screen name='ModalInit' component={ModalInit} />
  </Stack.Navigator>
 );
}

Далее берем созданный стек и помещаем его в root-стек, которому опять делаем фон прозрачным. Отключаем анимацию и выбираем stackPresentation = ‘transparentModal’.

// Root.tsx
import {createNativeStackNavigator} from 'react-native-screens/native-stack';
import {ModalsNavigationStack} from './ModalStack';

const Stack = createNativeStackNavigator();

const routes = [
 <Stack.Screen
  key='Modals'
  name='Modals'
  component={ModalsNavigationStack}
  options={{
   stackPresentation: 'transparentModal',
   stackAnimation: 'none',
   contentStyle: {backgroundColor: 'transparent'}
  }}
 />
];

/**
 * Рутовая навигация
 */
export function RootNavigation() {
 return <Stack.Navigator initialRouteName='Modals'>{routes}</Stack.Navigator>;
}

Только теперь будет работать кастомная модалка.

Еще одна проблема react-navigation: когда приходим на экран из другого стека, в нативном хедере на Android есть кнопка «Назад», а на iOS нет.

Особенность навигации на React
Особенность навигации на React

Поэтому для iOS  добавляем кастомную кнопку сами: https://github.com/software-mansion/react-native-screens/issues/576 

Следующий момент не проблема RN, а особенность разработки на iOS. Для проверки добавления карты в Wallet сначала нужно пройти сертификацию Apple и получить право токенизировать карты.

Проверить токенизацию карты в Wallet не получится, пока не пройдено ревью.

Тестирование взаимодействия с Apple Wallet
Тестирование взаимодействия с Apple Wallet

Проходим ревью, получаем статус «Ожидает релиза». Перед релизом в консоли Apple можно выпустить до ста промокодов. По промокоду можно установить приложение из App Store и вот там протестировать логику токенизации. После этого в любой продуктовой сборке можно проверять токенизацию карт.

Выводы

  • RN — платформа, которая довольно быстро развивается. Нужно учитывать, что она еще сыровата, хотя и готова к выходу на продакшн.

  • Реализация сборки под iOS лучше, чем под Android. Если ваш главный потребитель — пользователь Android, стоит посмотреть другие варианты. Если нужны обе платформы или в приоритете iOS, RN отлично подходит.

Если посмотреть на изменения RN и его приложения за год, заметен рост. Правки issue ведутся, добавляется новый функционал, значит у платформы большой потенциал.

Мой рассказ основан на опыте использования RN на задачах ЮMoney. Поделитесь вашими впечатлениями от React Native в комментариях или задавайте вопросы по моему обзору.

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


  1. Paperdoll
    06.08.2021 13:06

    А что скажете про expo.io? И как сейчас дела обстоят с интеграцией сторонних модулей в RN проекты?


    1. ananas1357 Автор
      06.08.2021 13:10

      Честно говоря, Expo.io до сих пор использовали только в примерах сторонних либ, чтобы посмотреть на работу. Для своего процесса разработки у нас настроен процесс доставки приложений, поэтому пока он нам не нужен.
      Интеграция сторонних модулей, как на вебе, для обычных js пакетов, для пакетов с нативным кодом сейчас очень много автоматизировано и не требуется дополнительных действий, кроме установки npm i, либо в readme подробно описано, что нужно добавить. Иногда приходится в нативной части вносить правки, для работы сторонних модулей, здесь уже нужны некоторые знания нативной части, но ничего сверхъестественного.


    1. ainu
      07.08.2021 10:12

      Меня не спрашивали, но я отвечу.

      1. Expo в режиме управляемого приложения уже не рекомендую.

      2. В режиме Bare (когда сами собираем приложение) это дело в последних версиях сильно стабильнее и удобнее. В этом режиме сторонние библиотеки ставятся без проблем, но в саммо expo есть множество родных вещей, стабильно работающих, и ставить стороннее нужно все меньше и меньше. Пуши, например. Доставка по воздуху средствами Expo сохраняется.