Внутренняя жизнь React Native

React Native  —  это библиотека JavaScript для создания мобильных приложений, работающих как на Android, так и на iOS. Как говорят создатели: “Научитесь однажды  —  пишите где угодно.” На протяжении двух последних лет она занимает ведущие позиции на рынке и уступать их никому не собирается. Как бы сильно я ни любила Flutter, в какой-то момент мне пришлось признать достоинства React Native и ее потенциал для создания действительно классных проектов. 

Источник: statista

В статье мы рассмотрим, как данная библиотека выполняет JavaScript на устройствах Android и iOS.

Вы узнаете: 

  1. Как происходит рендеринг UI приложения React Native.  
  2. Как код JavaScript объединяется в один общий файл (бандл) и преобразуется в приложение Android/iOS. 
  3. Как устройство Android/iOS понимает JavaScript. 
  4. Как нативный поток взаимодействует с потоком JavaScript. 
  5. Краткий анализ производительности приложения React Native. 

1. Рендеринг UI

Для начала проясним два момента.

  • Да, рендеринг приложения React Native происходит с помощью нативных представлений (views). 
  • Нет, код JavaScript не компилируется в родной язык платформы. 

Почему? Все просто. Как сможет телефон перевести такой слабо типизированный язык, как JavaScript, в сильно типизированный по типу Java или Objective C?

Вы пишите код UI в React Native, используя компоненты и соблюдая однонаправленный принцип, характерный для React. У вас есть дерево компонентов, взаимодействие между которыми осуществляется посредством свойств и обратных вызовов. Если родительскому компоненту что-то требуется от дочернего, то обратный вызов передается последнему. Таким же образом передается свойство дочернему компоненту, если ему что-то понадобилось от родителя. 

Пока вы следуете этому принципу, и вам не нужно смешивать подход React Native к созданию компонентов с нативно написанными представлениями, то вы будете создавать привлекательные UI без каких-либо затрат. Однако в случае необходимости написать нативный код для чего-то вроде MapView библиотека поможет вам наладить двухстороннее взаимодействие между ним и RN.

С этим вопросом все понятно, разбираемся дальше. Как OS понимает код JavaScript? 

2. Процесс бандлинга 

Нативное мобильное приложение создается с применением языка программирования, характерного для конкретной платформы. При разработке на React Native вполне можно обойтись без написания кода Objective-C/Java, если только перед вами не стоит задача, выходящая за рамки библиотеки, например интеграция платежного провайдера, предоставляющего только SDK для Android и iOS.

Однако любой проект React Native включает директории ios и android. Они служат точками входа для платформы и, по сути, загружают RN. Эти директории содержат характерный для каждой из них код, и именно здесь код JS с ними соединяется. 

Как правило, приложение запускается с помощью yarn android или yarn ios, после чего остается дождаться его волшебного открытия на выбранном устройстве. Но что же происходит в процессе ожидания?  

При вводе одной из команд, а именно react-native run-android и react native run-ios, вы запускаете сборщик, одним из которых является Metro. Он берет весь код JS и размещает его в одном файле main.bundle.js. Когда приложение в итоге открывается, телефон ищет его в привычном для него месте: в директории android или ios. Это и есть нативная точка входа, о которой шла речь выше. Она запускает движок JavaScript в потоке, в котором затем будет выполняться объединенный код, содержащийся в main.bundle.js.

Этот код будет взаимодействовать с нативным потоком с помощью моста (bridge) React Native.

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

3. JavaScriptCore

Для этих целей как раз и существует фреймворк JavaScriptCore. На устройствах iOS он предоставляется непосредственно OS. А вот на устройствах Android его нет, поэтому React Native поставляет его в комплекте с самим приложением. Это приводит к незначительному увеличению размера, но в конечном итоге особой роли это не играет. 

Обратим внимание на один момент, который позволит сэкономить время при отладке. JavaScriptCore применяется для выполнения кода JS при запуске приложения на устройстве. Однако если вы решите провести отладку этого приложения, то код будет выполняться в Chrome. Данный браузер использует движок V8 и WebSocket для взаимодействия с нативным кодом, поэтому вы сможете видеть важную информацию, такую как корректно отформатированные логи и генерируемые сетевые запросы. Просто не стоит забывать о существующих различиях между движком V8 и JavaScriptCore. Это разные среды, и появление ошибок возможно только при подключенном отладчике, а не при обычном запуске приложения на устройстве! 

Я использую отладчик только при необходимости. По опыту знаю, что при его подключении работа приложения нарушается. С подобной ситуацией я столкнулась в процессе создания календаря посредством библиотеки react-native-calendar от Wix. Метод onDayPress просто отказался работать. При этом я не могла понять, где кроется ошибка! Тщательнейшим образом изучила и перечитала документацию библиотеки  —  все было сделано правильно. Однако у меня был подключен отладчик, и во время отладки кода метод не работал. 

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

4. Мост React Native 

Мост RN, написанный на Java/C++, обеспечивает взаимодействие между основным потоком приложения и потоком JavaScript, используя для этого пользовательский протокол передачи сообщений.  

Поток JavaScript решает, что должно быть отображено на экране. Например, он дает указание основному потоку отрисовать кнопку и текст, для чего использует мост. Сообщение отправляется в формате сериализованного JSON. Оно не только устанавливает, что отображать на экране, но еще и где. Именно здесь в игру вступает теневой поток, который запускается вместе с потоком JavaScript и помогает вычислять расположение представлений. Результаты передаются в вышеупомянутом сообщении, которое посредством моста отправляется в основной поток.  

Все действия, совершаемые пользователем в отношении UI, происходят в основном потоке. Нажатие на кнопку, переключение состояний  —  любое из них должно быть сериализовано и отправлено с помощью моста в поток JavaScript. Именно здесь выполняется вся логика приложения. 

5. Влияние на производительность 

Обобщим все вышесказанное. Пользователь нажимает на кнопку. Основной поток распознает это действие и передает его в виде сообщения в поток JavaScript. Здесь происходит обработка логики, за которым следует соответствующее изменение UI. Теневой поток определяет, где произошли эти изменения, после чего обновления в формате сообщения отправляются обратно в основной поток. Как правило, пользователь не слишком быстро нажимает на экран, поэтому проблем с производительностью в стандартных сценариях использования не предвидится  —  мост довольно быстро обрабатывает результаты взаимодействия потоков. 

Отличительное преимущество React Native (по сравнению с другими платформами, например Cordova) состоит в том, что он не выполняет код в WebView, а использует нативные представления. Это означает, что у нас есть возможность разрабатывать стабильные и быстрые приложения, работающие со скоростью 60 кадров в секунду. Если вы меняете состояние компонента из верхней части дерева (не предотвратив ненужные повторные отрисовки), то заново будет отображаться все дерево целиком. В большинстве случаев пользователь этого не заметит. Но если дочерние компоненты ресурсозатратные, приложение начнет понемногу сбоить.

Заключение

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

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

Благодарю за внимание! 

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Bianca Dragomir: React Native: Under the Hood

Предыдущая статьяКомбинаторы парсеров: от parsimmon до nom (Typescript → Rust)
Следующая статьяPython для начинающих: какая разница между tuple, list и set?