Критика работы с React Native: наш опыт

При выборе языка React Native ожидается, что разработка одного приложения под две платформы займёт в два раза меньше времени, чем разработка двух приложений. Но по итогу оказывается, что разработка занимает столько же (если не больше) из-за сложностей, скрытых под внешним блеском и маркетингом.

В свете последних тенденций на рынке разработки ПО Android разработчик BytePace, Виктор Кинько, расскажет о некоторых подобных сложностях, с которыми ему пришлось столкнуться за последние несколько месяцев работы с React Native.

React Native адаптирует javascript под разработку для мобильных устройств. Это достигается тем, что для сборки проектов он использует несколько сборщиков - Metro Bundler, который интерпретирует JS-код и представляет ресурсы и сборщик целевой системы. В моем случае это был gradle для android. В теории приложение react-native должно запускаться довольно просто. Команда react-native run-android включает metro bundler и выполняет сборку приложения для всех подключенных android-устройств и эмуляторов.

В реальности оказалось, что даже на этом этапе есть сложности. На нашем проекте постоянно возникала ошибка "Unable to download JS bundle", которая означала, что bundler не может транслировать код в нативный. Как позже выяснилось, из-за того, что он не запустился. StackOverflow подтвердил догадки и подсказал, что стоит запускать bundler отдельным потоком с помощью команды react-native start. Это позволяет перезапускать bundler только если поменялся package.json, потому процедура не сильно замедляет разработку.

Package.json - это файл, содержащий набор внешних модулей для приложения. На npmjs.com находится большое количество различных библиотек для react-native, расширяющих функционал и упрощающих разработку. Многие библиотеки (например, firebase) используют нативные функции, а потому должны быть связаны напрямую с нативным кодом. Для этого используется команда react-native link <library-name>, которая должна настраивать эти связи с нативным кодом.

Из-за того, что все библиотеки пишутся в разное время, они используют разные версии SDK и требуют разного подхода. Иногда бывает так, что библиотеки несовместимы друг с другом, или последняя версия библиотеки оказывается экспериментальной, и сами разработчики советуют понизить версию до предпоследней. Довольно часто link не настраивает все требуемые зависимости. Так, для вышеупомянутого firebase требуется добавить множество дополнительных библиотек в нативном коде, подключить различные внешние репозитории, модифицировать mainApplication.java (и это только для android!). Для firebase есть достаточно понятная инструкция по выполнению этих действий, но для других библиотек она присутствует не всегда.

После того, как связи с нативным кодом настроены, можно собирать проект в надежде, что подключенная библиотека заработает. При сборке стоит помнить, что если вы получаете ошибку, то стоит удостовериться, что она возникла именно из-за ваших действий, а не из-за ошибки сборщика. Для полной уверенности стоит выполнить следующую последовательность действий:

• rmdir node_modules /s /q && npm cache clean-force && npm i

Данная команда удалит папку node_modules, а затем загрузит её заново. Это одна из самых долгих задач, потому стоит использовать её крайне редко. На некоторых проектах node_modules может занимать до нескольких гигабайт на жестком диске, а потому переустановка займёт время.

• rmdir android/app/build /s /q

В ходе разработки было замечено, что часто неудачный билд - следствие того, что сборщик не может создать (или удалить) папку из директории debug. Данное действие решает проблему того, что react не может самостоятельно удалить папку. Но в то же время генерация файлов для этой папки с нуля опять же займёт дополнительное время.

• react-native start-reset-cache

Запустить Metro Bundler. Эта вкладка должна оставаться открытой на протяжении всего процесса отладки. Если случилась ошибка, то лог ошибки может появиться здесь. Скорее всего при ошибке этот процесс завершится, и его снова нужно будет перезапустить.

• react-native run-android

Установить приложение на подключенное устройство или эмулятор. Большинство ошибок сборки случаются именно здесь, и часть из них понятна, но часть довольно иррациональна и "лечится" перезапуском всего процесса.

Представим процесс сборки последовательностью команд для одного проекта (уже имеющего realm, redux, react-navigation, ещё около десяти библиотек) после подключения firebase.

rmdir node_modules /s /q && npm cache clean-force && npm i react-native start react-native run-android >> не удаляется папка debug react-native run-android >> ошибка, metro bundler закрыт react-native start react-native run-android >> не удаляется папка debug react-native run-android >> установка успешна! Но metro bundler закрылся, а потому js-код не читается react-native start >> после нажатия restart в установленном приложении на телефоне оно загружается и наконец-то работает

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

Кстати об отладке. Отладчик React Native имеет проблемы не только с запуском. Исправление ошибок, найденных вследствие теста, тоже довольно болезненный процесс. В react-native JS-код транслируется в Native-код, но в процессе трансляции обфусцируется. Так что если не хотите видеть ошибки типа "null pointer exception in zzz.yyy()", то нужно пользоваться встроенным отладчиком, не получится просто читать exception'ы в logcat. При ошибке отладчик показывает красный “экран смерти” с её описанием, более-менее подталкивающим в сторону пути исправления. Но и с этой частью есть проблемы.

Хорошо, когда ошибка выглядит так:

Здесь действительно понятно, что происходит - вместо ожидаемого объекта массива в переменной this.state.noteArray.map находится undefined, из-за чего возникает пресловутая TypeError. Исправить её можно переходом на app.js:14 и проверкой значения в данной переменной перед использованием.

Хуже, когда ошибка выглядит так:

Так:

Или так:

Изображения взяты из интернета, но я видел их и "вживую". И несмотря на то, что они показываются в runtime, эта ошибка не связана с тем, что что-то было сделано неверно именно в вашем коде. Это может быть следствием того, что вы неверно установили библиотеку, или если в ваших импортах есть несовместимые зависимости, или что-то пошло не так в native-коде, а ошибку пытается отловить React. Каждая ошибка индивидуальна и решается крайне по-разному. Хорошо, что существует StackOverflow и хоть какой-то режим отладки.

Еще хуже, когда ошибка не воспроизводится в debug. С данной ситуацией я столкнулся при попытке собрать приложение с новой версией React с поддержкой x64-архитектур для Android. При установке приложения с дебаггером всё работает отлично. Но как только я делаю сборку тестеру на телефон, всё прекращает работать и ломается как только доходит до взаимодействия с базой данных. Чтобы отладить неотлаживаемое, на скорую руку используем консольные сообщения, в роли которых в данном случае выступал компонент react toastAndroid. Этот компонент выводит на экран короткий текст по достижению определенной строчки кода. Методично, желательно деля код пополам, локализуем функцию, в которой происходит ошибка, и выясняем, что метод Object.assign({}, item) не работает в новой версии React. Повезло, что можно было заменить эту функцию на более короткую {...item} при сохранении функционала приложения, но поиск этой ошибки обошелся в примерно десяток часов работы.

После было проведено небольшое исследование в поисках причин. Как обнаружилось, для интерпретации JS-кода в debug и production версиях React Native использует разные Javascript-движки: для отладки Chrome JS engine, а в работе JavaScriptCore. Да, React Native не переводит JavaScript в нативный код, а интерпретирует по ходу выполнения. При этом отладочный движок работает куда стабильнее, а потому баги всё чаще прокрадываются в production. К примеру, в этой статье показано, как форматирование даты работает в разных условиях. Возвращаясь к ошибке: так вышло, что после обновления версии React Native веб-движок production-версии потерял поддержку Object.assign(). А отладочный движок остался тот же.

Пожалуй, худший вариант - это случай, когда приложение ломается в случайных местах, только в production-версии и без каких-либо логов со стороны React Native. Пример: после установки релизной версии приложения на телефон оно "работает какое-то время", а потом "выключается без ошибки или предупреждения в случайный момент". Причём ошибка воспроизводится не на всех устройствах. В конце концов, методом проб и ошибок (и обнаружением того, что вышеупомянутый Firebase Crashlytics не присылает соответствующих ошибок) удалось выловить логи падения, которые выглядели так:

Ошибка "Android crashes: signal 11 (SIGSEGV)"

Этот текст даже не относится к нашему приложению, он даже не был отмечен красным. Но после того, как я его получил и отправился на форумы, я обнаружил, что новая версия React Native сломана. И предыдущая была сломана. На официальном Issue Tracker ошибка "Android crashes: signal 11 (SIGSEGV)" существовала уже два месяца, и к моей удаче за два дня до того, как я обратился туда(!), было предложено экспериментальное решение, которое исправило ошибку.

Иронично, что некоторые разработчики, которым приходилось сталкиваться с Android Studio, были в недоумении по поводу того, что в IDE есть такие опции как build/clean project или file/invalidate caches. Это требуется для того, чтобы избавиться от аномального поведения gradle, от ложных сообщений об ошибках и предупреждениях, от ошибок синхронизации. Разработчики спрашивали: "почему мы должны делать работу за нашу IDE, в таких ситуациях эти команды должны выполняться автоматически". И их можно понять, но в то же время современные IDE и так делают всю сложную работу за кадром. А эти разработчики попросту не работали с React Native.

Всё рассказанное - это единичные случаи, случившиеся за последние несколько недель. Здесь я не описываю сложности запуска приложений с Expo, с настройкой стиля кода в babel/eslint, не ругаю Javascript за излишнюю гибкость, не рассказываю как на одном из проектов почти полностью пропала возможность отладки из-за связки redux/realm. Учитывая описанные сложности поддержки и разработки и тот факт, что для двух систем это всё умножается на два, стоит задуматься, действительно ли React Native выгоден для разработки? После того как мы завершили наш третий проект на этом языке, мы решили, что нет. Как вы считаете?

0
23 комментария
Написать комментарий...
Влад Якупов

Все ваши проблемы с рн из за того что вы не удосужились почитать официальную доку, либо читали невнимательно, в частности то что в дебаге код запускается в v8, а не в jsc.

Ответить
Развернуть ветку
Vasily MaestroVS

Меня на реакт подбил знакомый, работающий с React JS. До этого работал на на нативе android , iOs.
Достаточно легко было нарисовать простой список элементов, получаемых с сервера. Но когда дело пошло дальше: новостная лента, вебвью, перехват и обработка запросов, работа с БД - началась адская боль и страдания. Наверное надо быть js ником в третьем поколении, чтобы это выдержать. Война с версиями библиотек. Под айос еще как-то... pod install и все. Но с андроидом с градлом. Не в градле дело, а в вечных конфликтах версий... Да, запустилось на андроиде и не идет на айос. И наоборот. Куча кастомных приблуд. Куча ограничений. Не советую никому, кто будет писать что-то функциональное, мало-мальски сложнее презентационной странички. Тем более если вы отлично дружите с нативной разработкой на типизированных языках. После реакта пропало желание смотреть в сторону Flutter и прочих кросплатформенных приблуд. Пока что углубляюсь в котлин и свифт.

Ответить
Развернуть ветку
Александр Савинов

Довольно.. забавное описание.
Ощущение, что процесс разработки происходил на линукс с его вечным цирком в плане прав доступа.
Бандлер обычно запускают отдельной командой, т.к это удобней( и можно почистить его кеш в случае странностей, т.е npm start — --rest-cache )
Часть ошибок( из показанного на скринах ) - ошибки связанные именно с JS. К примеру, вместо массива прилетел с бука объект или вообще null. Как ни странно, метода map у него не будет и попытка выполнения его вызовет ошибку.
Еще часть - когда отладка происходит на подключенном к ПК телефоне( обычно помогает adb reverse )
Далее, по крашам. Если приложение просто вылетает( тихо выключается ) - это сбой в нативной части. Если вместо приложения - вдруг красный(отладка) / белый(релиз) экран - сбой в JS части.

Package.json - файл, относящийся к npm, т.е пакетному менеджеру.
Штука позволяет довольно удобно настраивать версии пакетов.
npm - это не чисто RN-приблуда. Соотв., на npmjs полным-полно модулей, к RN не относящихся( там и для React.JS модули есть и для Node.JS и для многого другого )

Да, подключение модулей( особенно, файербейса и ФБ/Твиттера/Гугла - это обычно самая боль ) не всегда происходит запросто. Однако в разработке, подключение модулей происходит нечасто. Это не то действие, которое выполняется ежедневно и по 10 раз в день. Если вы подключили файербейс к андроиду, второй раз это делать не придется.
Если это сколь-нибудь большое приложение, то подавляющая часть времени - это  работа с версткой( и эта часть реально сильно сокращается в сравнении с независимой разработкой для 2 платформ ).

Основная часть ошибок, связанных с работой при подключенном отладчике и не_работой - без отладчика, обычно относится к работе с сетью.
При подключенном отладчике, JS код исполняется на полноценном подобии браузерного движка( т.е даже с некоторыми соотв. глобальными объектами и всякими плюшками при запросах ), без него - соотв., без перечисленного.

Основани часть ошибок, связанных при установке-подключении модулей, возникает из-за непонимания очевидных вещей:
Если был подключен нативный модуль( т.е был выполнен react-native link ), очевидно, что проект нужно полностью пересобрать. Желательно, почистить от старой сборки. Особенно проблемами с этим грешит android и gradle( этот "гений" создает столько мусора, что сам порой в нем разобраться не может ).
Если был подключен просто_JS модуль( т.е просто установлен через пакетный менеджер, без линковки ), полная переборка не требуется достаточно перезагрузить приложение( всм, Reload JS ).
В обоих вариантах лучше перезапустить бандлер( прибить и выполнить npm start — --reset-cache ).

п.с: сейчас для RN проектов никто npm не пользуется - пользуются yarn'ом, который несравненно быстрее.
п.с[2]: за Object.assign еще года 2 назад уже вовсю били.. ногами.. по лицу..
Это примерно аналогично тому, если бы всерьез ругались, что на винде десятке видите ли не работают программы времен DOSа, а так хотелось затолкать их в свежий релиз своего ПО.
п.с[3]: Expo надо обходить стороной

Ответить
Развернуть ветку
Henry Pro

Отдельно отметил бы вёрстку в RN - это только псевдо верстка под браузеры. На самом деле очень много нюансов есть и часто приходится юзать платформозависимые стили.

Ответить
Развернуть ветку
Дмитрий Назаров

А даёт ли это какой-то результат в скорости разработки или качестве?..

Ответить
Развернуть ветку
Exey Panteleev

Как написавший 3 аппы в 2012-2014 годах, на более качественной с точки зрения перфоманса(хороший мультитрединг), кроссплатформе Adobe AIR, я ушел все равно в чистый нейтив. Сегодня просто божественные инструменты для того, чтобы силами одного разраба сделать Swift и Kotlin нейтив: кодогенерации в оба языка, схожие UI-либы, даже местами можно копипастить код как есть. Ни один сегодня опытный разраб не вляпается в кроссплатформу.
Правда есть одна работающая бизнес-модель кроссплатформы, это Unity для игр, но там надо много платить — только на таких финпотоках можно поддерживать кроссплатформенный движок в актуальном состоянии

Ответить
Развернуть ветку
Henry Pro

Вы знаете, я в итоге пришел к тому, что для MVP лучше использовать не RN, а RN + webview. Я не представляю, как делать на RN сложные графические вещи, анимации и пр. Исходя из этого делаю предположение, что RN - это больше про аналог мобильных версий сайтов завёрнутых в приложение. Так вот вёрстку и бизнес логику можно заменить вебвью, а от RN использовать только нужное: пуши, геолокацию и другие специфические штуки. И вот этот подход очень сильно ускоряет разработку,т.к. в итоге нужно соблюсти только кроссбраузерность(safari + chrome).

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

PSS
Надеюсь webview не запретят.

Ответить
Развернуть ветку
Дмитрий Назаров

Может быть вам больше подойдёт Cordova? Мы на Cordova даже виртуальную реальность реализовали...

Ответить
Развернуть ветку
Henry Pro

Можно попробовать конечно, но тут все зависит от задачи. На мой взгляд, если можно обойтись вебвью, то лучше ее и использовать. А на счёт чем ее обернуть - лучше наверное нативные языки, это не долго и не дорого.

Ответить
Развернуть ветку
Дмитрий Назаров

Не так давно попробовали Flutter. Пока до релиза не дошло дело, но руководству нравится. Хорошая скорость разработки, качественная адаптация под разные форматы устройств. Поддержка от Google ни в какое сравнение с поддержкой React Native. На мой взгляд, достойная замена React Native, который до релиза, скорее всего, не доживёт.

Ответить
Развернуть ветку
Евгений Малаховский

Интересно было почитать о вашем опыте. Часто чешутся руки его изучить, особенно после комментариев под моей очередной статьей об Ionic Framework, что React Native круче.

Расскажите как сложно верстать кастомный дизайн приложения в React Native? Само собой в сравнении с нативом.

Ответить
Развернуть ветку
BytePace
Автор

Здравствуйте, спасибо за отзыв!
С Ionic совсем не знаком, потому не знаю насколько React лучше или хуже, но опыт несомненно интересный.

Если говорить о дизайне, то React Native в этом плане очень похож на верстку для веба с использованием css и Flexbox. Стоит заметить, что чаще всего лейауты связаны с функционалом, то есть у каждого компонента есть список входящих атрибутов, которые он поддерживает, что заставляет разработчика обеспечивать модульность при создании своих компонентов. Довольно оригинально и в то же время странно ощущается система обновлений элементов, когда к ним применяются новые атрибуты. С этим пришлось свыкнуться, поначалу разбирались и с тормозами, и с отсутствием перерисовки элементов по этой причине.

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

В итоге всё зависит от специфики приложения. Если это просто набор списков и кнопок, то в среднем верстка получится быстрее, чем на нативном языке. Если встретится что-то сложнее, или у вас с дизайнером взаимная ненависть, то придётся изучать хитрости и особенности React Native, подстраиваться под частные случаи и стили на конкретных платформах, что заставит потратить дополнительное время, которое, в зависимости от опыта может быть значительным.

Ответить
Развернуть ветку
Дмитрий Назаров

В своё время мы тоже прошли этот путь, от Cordova до наивной разработки. В начале у всех разработчиков, пришедших из веба, есть боязнь использования каких‐то Kotlin и Swift, но все не так страшно на практике, и даже затягивает )) Многие англоязычные блогеры замечают, React Native хорош ... для создания прототипа. Большие проекты очень тяжело поддерживать в RN. Далее будут всплывать проблемы с переходом от старой версии RN к более новой...

Ответить
Развернуть ветку
volkov

FB app же вроде на RN написан - большой проект, но поддерживается

Ответить
Развернуть ветку
Дмитрий Назаров

Ещё хочется добавить. Жирный минус в RN, по крайней мере для iOS, отсутствие поддержки контроллеров. Мы работали с дополненной реальностью, и был готовый контроллер из ARKit... Модуль для RN в этом случае поступает просто, создаёт статический класс ARView, который живёт всегда. Приходится как-то останавливать его... короче, убийца Айфона (его батарейки )))

Ответить
Развернуть ветку
Александр Савинов

RN вроде и не предназначен для подобного рода приложений( игры/дополненная реальность )
Для них логичней какой-нибудь Unity или Unreal попробовать

Ответить
Развернуть ветку
Sergey Shirnin

Как насчёт flutter?

Ответить
Развернуть ветку
BytePace
Автор

Пока не пробовали. И не планируем пока что.

Ответить
Развернуть ветку
Henry Pro

На самом деле это мэйнстрим сейчас. Советую попробовать, если вы занимаетесь разработкой мобильных приложений. Поговаривают, что это убийца всех остальных кроссплатформенных решений. Dart только немного смущает...

Ответить
Развернуть ветку
Денис Никаноров

Решили перейти на нативные приложения или на другой фреймворк?

Ответить
Развернуть ветку
BytePace
Автор

Перешли полностью на нативные решения. Осталась только поддержка уже выпущенных на RN приложений.

Ответить
Развернуть ветку
Георгий Хромченко

- Вообще не понял про проблемы с Expo. Проблем не больше и не меньше чем в обычном RN,  по сути это просто скомпилированное RN приложение куда можно легко подгружать свой JS. Возможность обновлений по воздуху - просто киллер фича, сэкономила просто адское количество часов на тестировании.   Для входа вообще идеальное решение, на мой взгляд. Потом можно сделать eject и работать в двух средах - задачи по верстке все равно можно смотреть в Expo.

- Поскольку основную разработку веду в Expo, то никаких вот этих r

 - Проблемы в нативных библиотеках есть, просто много сырых левых native библиотек. Я решил по минимуму связываться с native, только в случае необходимости, остальное держать в JS стеке.  

- Про то что отладка идет в Chrome и поэтому все работает - ну как бы известный факт, можно в сообществах поспрашивать.  Также дико полезный инструмент - Reactotron, чтобы видеть все вызовы к API, сообщения к Redux и так далее. В 0.62 запилили Flipper, но я еще не пробовал

- Багов, ошибок и "нюансов" overдофига. Чем более выпендрежное решение хочется сделать, тем сложнее. Чем больше нативных библиотек, тем больше риск глюков, просто потому что. react-native-gesture-handler может не работать в модалках,  маркеры на картах могут не цеплять иконки нужного размера, вообщем я думаю могу десятки доставших глюков описать. Это реально достает, похоже что я просто привык к слегка "подглючивающей" среде.

- Настоящая проблема для создания качественных приложений - тотальная асинхронность и бридж - отдельная прослойка между нативным кодом и JS слоем. Между произошедшим событием и попаданием его в JS слой могут пройти десятки миллисекунд, и это усложняет построение няшных отзывчивых интерфейсов. Обещают исправить в 2020, но я больше верю в 2021, а потом когда еще создатели библиотек подтянуться - анонс безбриджевого взаимодействия был еще в 2018, с тех пор "пилят".

- Babel, eslint и прочее - ну вообщем это стандартный js набор, все уже привыкли. Обычно все уже "как-то" сгенерено по дефолту. Конфигурируется кропотливо под проект или под команду.

- Важно понять, что там внутри мобильная платформа, а не веб. Что View это не Div, и что управляем не дом нодами а нативными элементами. 

Ответить
Развернуть ветку
Portenko Dmitriy

Мне все таки кажется, что критику должен писать специалист, который имеет богатый опыт и знает все  тонкости и нюансы технологии. А так вы попробовали у вас что-то не получилось, это нормально. Когда вы новичок в технологии, то будете сталкиваться с подобными проблемами.  

Ответить
Развернуть ветку
20 комментариев
Раскрывать всегда