Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Привет! Я Илья, фронтенд-разработчик. В ЮMoney работаю четыре года. Занимался личным кабинетом интернет-магазинов на B2B-продукте ЮKassa. Последний год развиваю продукт для расчетно-кассового обслуживания ЮBusiness.
Что значит РКО
Юридическому лицу нельзя просто открыть счет в банке: нужен специальный счет. Закон регулирует денежный оборот компаний, поэтому созданы специальные системы для движения денег юрлиц. Одна из них — наш сервис ЮBusiness.
Есть веб-версия, есть приложения в App Store и Google Play.
Я приоткрою мир разработки на 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. Запускаем сборку на выбранный симулятор и разрабатываем.
Для работы с реальным устройством понадобятся:
Xcode;
iPhone;
учетная запись разработчика в App Store, добавленная в компанию — разработчика приложения;
UDID iPhone, добавленный в разрешенные девайсы для тестирования приложения;
подключение по проводу к компьютеру для сборки приложения на iPhone.
Для Android на симуляторе требуется только Android Studio. Запускаем сборку на выбранный симулятор и… И все, разрабатываем.
Для работы с реальным устройством понадобится только Android Studio и устройство. Android можно собирать в дебаг-режиме на устройстве по общему Wi-Fi, не подключая смартфон к компьютеру по проводу.
У React Native есть дефолтный дебаггер. Он открывается в любом браузере и позволяет следить за консолью, профилировать приложение, использовать точки остановки — и на этом все. Этого функционала для полноценного debug катастрофически мало.
Следующий вариант — React Native Debugger: https://github.com/jhen0409/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.
Работа со стилями на голову выше, чем у аналогов, из коробки есть просмотр логов/сетевых запросов. Можно устанавливать модули для Redux Store и писать их самому. Мы с ним еще не подружились, но в процессе.
Проблемы и особенности
Теперь перейдем к интересным багам и особенностям. Забегу вперед и скажу, что раньше было гораздо больше багов, но их пофиксили мейнтейнеры. Первый важный момент: React Native еще даже не мажор, поэтому стабильность не на высоте.
Начну с одной из первых задач, с которой столкнулся, придя в команду. Дизайнер нарисовал заголовки и подзаголовки разной жирности: 400, 700, 800. Я открыл симулятор на iOS, добавил стили, что может быть проще? Но! Тестировщик вернул задачу обратно, сказал, нет жирности на Android. Я открыл симулятор Android и действительно: нет жирности у заголовков. Дальше я погуглил: естественно, это известная проблема. В качестве решения нам предлагают создать много шрифтов разной жирности. https://github.com/facebook/react-native/issues/26193#issuecomment-525028689.
Мы поговорили с дизайнером и решили не использовать это.
В моменте в приложении на Android появилась едва заметная полосочка во время анимации нажатия.
Как ужасно это выглядит! Дальше повторилась ситуация из предыдущей проблемы. Гуглю, и что мы видим? Это известная проблема. https://github.com/facebook/react-native/issues/29010#issuecomment-636653305. Выходы такие: либо считаем размеры layout сами, либо используем костыльное решение: сдвиг на -1 пиксель.
KeyboardAvoidingView имеет ряд проблем с «прилипанием» элемента к клавиатуре.
Где не справляется обычный KeyboardAvoidingView:
на экранах iOS со сворачивающимся хедером (параметр headerLargeTitle: true);
на экранах, где нужен инпут, прилипающий к клавиатуре (из-за подскролла Android добавляет лишний паддинг).
Для навигации мы используем react-navigation: https://github.com/react-navigation/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 нет.
Поэтому для iOS добавляем кастомную кнопку сами: https://github.com/software-mansion/react-native-screens/issues/576
Следующий момент не проблема RN, а особенность разработки на iOS. Для проверки добавления карты в Wallet сначала нужно пройти сертификацию Apple и получить право токенизировать карты.
Проверить токенизацию карты в Wallet не получится, пока не пройдено ревью.
Проходим ревью, получаем статус «Ожидает релиза». Перед релизом в консоли Apple можно выпустить до ста промокодов. По промокоду можно установить приложение из App Store и вот там протестировать логику токенизации. После этого в любой продуктовой сборке можно проверять токенизацию карт.
Выводы
RN — платформа, которая довольно быстро развивается. Нужно учитывать, что она еще сыровата, хотя и готова к выходу на продакшн.
Реализация сборки под iOS лучше, чем под Android. Если ваш главный потребитель — пользователь Android, стоит посмотреть другие варианты. Если нужны обе платформы или в приоритете iOS, RN отлично подходит.
Если посмотреть на изменения RN и его приложения за год, заметен рост. Правки issue ведутся, добавляется новый функционал, значит у платформы большой потенциал.
Мой рассказ основан на опыте использования RN на задачах ЮMoney. Поделитесь вашими впечатлениями от React Native в комментариях или задавайте вопросы по моему обзору.