Разработка BytePace
990

Критика работы с 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 выгоден для разработки? После того как мы завершили наш третий проект на этом языке, мы решили, что нет. Как вы считаете?

Материал опубликован пользователем. Нажмите кнопку «Написать», чтобы поделиться мнением или рассказать о своём проекте.

Написать
{ "author_name": "BytePace", "author_type": "self", "tags": [], "comments": 16, "likes": 8, "favorites": 9, "is_advertisement": false, "subsite_label": "dev", "id": 77358, "is_wide": false, "is_ugc": true, "date": "Tue, 30 Jul 2019 07:27:46 +0300" }
{"average":28079,"one":95,"ten":75}
Сколько денег вы откладываете в месяц?
Ответьте и узнаете, сколько копят другие.
0 ₽
70 000+ ₽
0 ₽
{ "id": 77358, "author_id": 331975, "diff_limit": 1000, "urls": {"diff":"\/comments\/77358\/get","add":"\/comments\/77358\/add","edit":"\/comments\/edit","remove":"\/admin\/comments\/remove","pin":"\/admin\/comments\/pin","get4edit":"\/comments\/get4edit","complain":"\/comments\/complain","load_more":"\/comments\/loading\/77358"}, "attach_limit": 2, "max_comment_text_length": 5000, "subsite_id": 235819, "last_count_and_date": null }
16 комментариев

Популярные

По порядку

Написать комментарий...
1

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

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

Ответить
0

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

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

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

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

Ответить
1

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

Ответить
0

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

Ответить
0

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

Ответить
0

Как насчёт flutter?

Ответить
0

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

Ответить
0

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

Ответить
0

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

Ответить
0

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

Ответить
0

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

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

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

Ответить
0

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

Ответить
0

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

Ответить
0

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

Ответить
0

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

Ответить
0

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

Ответить
0
{ "page_type": "article" }

Прямой эфир

[ { "id": 1, "label": "100%×150_Branding_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox_method": "createAdaptive", "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfl" } } }, { "id": 2, "label": "1200х400", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfn" } } }, { "id": 3, "label": "240х200 _ТГБ_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fizc" } } }, { "id": 4, "label": "240х200_mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "flbq" } } }, { "id": 5, "label": "300x500_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfk" } } }, { "id": 6, "label": "1180х250_Interpool_баннер над комментариями_Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "bugf", "p2": "ffyh" } } }, { "id": 7, "label": "Article Footer 100%_desktop_mobile", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjxb" } } }, { "id": 8, "label": "Fullscreen Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjoh" } } }, { "id": 9, "label": "Fullscreen Mobile", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjog" } } }, { "id": 10, "disable": true, "label": "Native Partner Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyb" } } }, { "id": 11, "disable": true, "label": "Native Partner Mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyc" } } }, { "id": 12, "label": "Кнопка в шапке", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "p1": "bscsh", "p2": "fdhx" } } }, { "id": 13, "label": "DM InPage Video PartnerCode", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox_method": "createAdaptive", "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "bugf", "p2": "flvn" } } }, { "id": 14, "label": "Yandex context video banner", "provider": "yandex", "yandex": { "block_id": "VI-223676-0", "render_to": "inpage_VI-223676-0-1104503429", "adfox_url": "//ads.adfox.ru/228129/getCode?pp=h&ps=bugf&p2=fpjw&puid1=&puid2=&puid3=&puid4=&puid8=&puid9=&puid10=&puid21=&puid22=&puid31=&puid32=&puid33=&fmt=1&dl={REFERER}&pr=" } }, { "id": 15, "label": "Плашка на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byudx", "p2": "ftjf" } } }, { "id": 16, "label": "Кнопка в шапке мобайл", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byzqf", "p2": "ftwx" } } }, { "id": 17, "label": "Stratum Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fzvb" } } }, { "id": 18, "label": "Stratum Mobile", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fzvc" } } }, { "id": 19, "label": "Тизер на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "p1": "cbltd", "p2": "gazs" } } } ]
Приложение-плацебо скачали
больше миллиона раз
Подписаться на push-уведомления
{ "page_type": "default" }