Чаще всего при выборе этого языка ожидается, что разработка одного приложения под две платформы займёт в два раза меньше времени, чем разработка двух приложений. Но по итогу оказывается, что разработка занимает столько же, если не больше, из-за сложностей, скрытых под внешним блеском и маркетингом. Мы расскажем о некоторых подобных сложностях, с которыми нам пришлось столкнуться за последние несколько месяцев работы с React Native.
React Native адаптирует Javascript под разработку для мобильных устройств. Это достигается тем, что для сборки проектов он использует несколько сборщиков — Metro Bundler, который интерпретирует JS-код и представляет ресурсы и сборщик целевой системы. В нашем случае это был gradle для Аndroid. В теории приложение 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.
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 не присылает соответствующих ошибок) удалось выловить логи падения, которые выглядели так:
Этот текст даже не относится к нашему приложению, он даже не был отмечен красным. Но после того, как мы его получили и отправились на форумы, мы обнаружили, что новая версия 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 выгоден? После того как мы завершили наш третий проект на этом языке, мы решили, что нет. Как вы считаете?
Комментарии (21)
Geminis
26.09.2019 09:14+2Наконец то, кто то написал правдивую статью про то как «круто и удобно» писать приложения на react-native. Еще бы упомянуть как они issues закрывают без решения проблемы.
Groramar
26.09.2019 09:51Delphi FMX в помощь. Один код везде работает. FMX уже вылизали большей частью.
oxyii
26.09.2019 12:46+1Если честно, я в замешательстве. С выходом react-native 0.60 добрая половина вашей статьи устарела. Хотя бы в части линка зависимостей. Да и вообще, лично у меня после прочтения статьи возникло ощущение, что вы называете танк бесполезной боевой единицей только потому, что не умеете им управлять. Я с успехом применяю RN, уже не в первом проекте. Есть конечно и ошибки, и трудности… Но это говорит только о моей компетентности, а не об особенностях механизма.
Free_ze
26.09.2019 13:24Но это говорит только о моей компетентности, а не об особенностях механизма.
Если механимз при прочих равных требует особой компетенции, разве это не может так же указывать на надостатки его дизайна, неинтуитивность?oxyii
26.09.2019 14:16Любой механизм требует определенного уровня компетентности. Даже на детских конструкторах пишут возрастные ограничения. Это не означает, что ребенок, не достигший этого возраста, не может попытаться собрать этот конструктор. Лично для меня RN достаточно хорошо документирован и имеет довольно зрелое комьюнити, если документации не хватило. И потом, RN — это не только android и ios приложения. Это универсальный каркас интеграции React в различные платформы, включая десктопные маки, смарт-тв, "виндовсы". Поднимать тему react-native-web, думаю, даже не стоит. Это тема для отдельной статьи, а не объяснения в комментарии.
Free_ze
26.09.2019 14:29+1Любой механизм требует определенного уровня компетентности.
Вопрос в оправданности таких требований и трудозатратах на поиск решений.
Я с RN не знаком, но категоричность все же удивляет. Неужто технология без изъянов, просто все споткнувшиеся просто не смогли найти очевидных решений? Или все же есть подводные грабли, о которых и пишут?frog
26.09.2019 14:38Грабли конечно есть. Всякие странные ошибки связанные с тем, что где-то случайно не убился какой-то файлик и методы их решения в виде «перезапустить то-то и то-то 3-4 раза, а потом ещё это и перезагрузиться». Другое дело, что постепенно логику этих проблем начинаешь понимать и жить с этим становится вполне возможно.
oxyii
26.09.2019 14:56Давайте будем объективными: подводные грабли с булыжниками есть всегда и везде. Насчёт "споткнувшихся" — читайте выше: хорошая документация, достаточно отзывчивое незазвездившееся комьюнити. Всегда "пнут" в правильный раздел документации. Вы крайне занимательный собеседник: не зная сути оперируете аргументами из серии "а вдруг не получится". С таким скепсисом можно относится ко всему окружающему миру.
Free_ze
26.09.2019 15:27подводные грабли с булыжниками есть всегда и везде.
Пропорции на единицу полезной работы могут быть разными.
не зная сути оперируете аргументами из серии «а вдруг не получится».
Свой интерес подчеркнул — вы изначально слишком категорично (на мой взгляд) заявили практически следующее: «Технология отличная, просто вы все неосиляторы!»
Думаю, подобное и вы слыхали много раз относительно других инструментов, поэтому специфика RN здесь есть едва ли (=ogregor
26.09.2019 16:03Если проект продолжительный то обновление к новой версии, та еще задача. В свое время я перепрыгивал с 0.54 и далее, но после этого до 0.60 проапгрейдится не получилось (надо сказать что проект домашний, так что не было возможности тратить недели на поиск решения) мое мнение что RN еще не готов.
nochkin
26.09.2019 16:12У меня самый трудный переход был как раз с 0.59 на 0.60. Остальные (57/58) были лёгкие всегда. Даже 0.60-0.61 без проблем был.
С 0.59 на 0.60 перейти в результате не получилось — потратил час на разбор, а потом просто создал новый проект, скопировал директорию «src» и добавил пакеты. Процесс занял несколько минут.
oxyii
26.09.2019 16:25С моей стороны всё видится иначе. Автор, на мой взгляд, не разобрался во всех прелестях и тонкостях механизма. Написал вроде обзорную, вроде статью… Обмолвился полу-словом об обфускации, не разобравшись даже, что обфускация касается только нативных модулей. То, что автор называет обфускацией для js — это обычная минификация. И если ошибка пишет нечитабельными классами — воспроизведи ее без минификации js. Всё! И вот так практически по каждому тезису автора! На что я в двух словах и обратил внимание, сравнив этот подход с танком. Я категоричен лишь в том, что тему нужно либо раскрывать, либо не брать вовсе. А не хоронить надежды тех разработчиков, которые только собираются попробовать RN
VasiliySS
26.09.2019 12:46До React Native я не дошел, сравнить не могу. Остановился на Xamarin.Forms, и слезать пока не хочется — очень хорошо сделано, как по мне.
nochkin
26.09.2019 16:08Пусть я и не полностью согласен, но очень хорошая статья. Позитивный опыт у всех примерно одинаковый, а вот негативный различается. Всегда интересно про него узнать.
Видимо, мне очень повезло, но у меня точно время разработки на двух платформах на React Native равна примерно 120% от одной платформы. То есть, экономия видна открыто.
Что самое интересное, у меня есть достаточно долгий опыт разработки на Android (Java), iOS (Obj-C) и даже на других платформах, где я всегда старался использовать родную среду.
На React Native у меня были долгие тёрки только в самом начале, так как я не сраз понял с чего вообще начать и как это устроено. Про React я на тот момент знал только название, а фо front-end'е у меня был нулевой опыт (я даже слабо представлят что такое npm и зачем он нужен). Сейчас у меня опыт React Native примерно 3-4 месяца.
Но даже потратив время на понимание процесса общее время на приложение значительно меньше разработки двух нативных. Не говоря о том, что некоторые вещи на RN написать проще и быстрее, чем на нативных платформах.
Изначально я пишу и проверяю на Android так как это первая платформа для запуска, но время от времени проверяю на iOS. Обычно надо только изредка подкрутить стили, редко когда что-то ещё.
Обычно документации и ответов на SO хватает для решения проблем. Пару раз просто лез в код библиотеки что бы понять как там устроено, но это обычно библиотеки сторонних разработчиков.
Кстати, получил много полезных знаний из чужого кода.
fleshold
26.09.2019 22:00Airbnb тоже не зашло. Подробности здесь (правда на английском), medium.com/airbnb-engineering/sunsetting-react-native-1868ba28e30a несколько статей
Due to a variety of technical and organizational issues, we will be sunsetting React Native and putting all of our efforts into making native amazing.
saag
Попробуйте для сравнения Flutter.
BytePace Автор
Пока не пробовали и не планируем пока что