О проекте по перепроектированию архитектуры React Native заговорили в 2018 году. Этой работой занимается команда Facebook. Цель перепроектирования заключается в том, чтобы сделать фреймворк более стабильным, и в том, чтобы решить наиболее распространённые проблемы, накопившиеся в RN за годы разработки. Материал, перевод которого мы сегодня публикуем, посвящён рассмотрению того, как переработка архитектуры RN способна улучшить производительность приложений и скорость работы программистов.
Старая архитектура
React Native — это пример решения, которое можно назвать «платформенно-независимым». В результате оказывается, что главная цель фреймворка заключается в том, чтобы позволить разработчикам писать JavaScript-код, использующий механизмы React. Задача React Native заключается в том, чтобы обеспечить преобразование дерева React-компонентов во что-то такое, что окажется работоспособным на различных мобильных платформах. Это означает следующее:
- Корректное формирование интерфейса.
- Обеспечение доступа к нативным возможностям различных платформ.
Обычно, в экосистемах Android/iOS, механизм, используемый для решения этих задач, выглядит примерно так, как показано на следующем рисунке.
Существующая архитектура React Native (источник)
В каждом React Native-приложении параллельно выполняются три следующих потока:
- Поток JS. Это — поток, в котором осуществляется чтение и компиляция JavaScript-кода. Здесь же выполняется основная часть логики приложения. Бандлер Metro комбинирует весь JS-код в единственный файл, отвечает за обработку JSX- и TS-конструкций. Затем полученный код отправляют движку JavaScriptCore, средствами которого этот код может быть запущен.
- Поток Native. Здесь производится выполнение нативного кода. Этот поток взаимодействует с JavaScript-потоком тогда, когда нужно обновить интерфейс или обратиться к нативным функциям. Этот поток можно разделить на две части. Первая, Native UI, отвечает за использование нативных средств формирования интерфейса. Вторая, Native Modules, предоставляет доступ к особым возможностям платформы, на которой работает приложение. Она готова к работе после запуска приложения. То есть, например, если React Native использует Bluetooth-модуль, он всегда будет активным, даже в том случае, если он, на самом деле, не используется.
- Поток Shadow. В нём выполняется пересчёт макета приложения. Здесь используется движок Yoga, являющийся собственной разработкой Facebook. В этом потоке выполняются вычисления, связанные с формированием интерфейса приложения. Результаты этих вычислений отправляют потоку, ответственному за вывод интерфейса.
Для организации взаимодействия между потоками JS и Native используется модуль Bridge (мост). Этот модуль написан на C++, в его основе лежит асинхронная очередь. Когда мост получает данные от одной из сторон, он сериализует эти данные, преобразуя в строковой вид, и передаёт их через очередь. Когда данные прибывают в пункт назначения, они десериализуются.
Это означает, что все потоки полагаются на асинхронные JSON-сообщения, передаваемые через мост. Каждая из сторон отправляет эти сообщения, ожидая (но не имея гарантии) того, что когда-то в будущем на эти сообщения будет получен ответ. При такой схеме обмена данными существует риск перегрузки канала связи.
Вот пример, который обычно приводят, чтобы проиллюстрировать то, как подобная схема обмена данными способна вызвать проблемы с производительностью приложения. Предположим, что пользователь приложения выполняет прокрутку огромного списка. Когда в нативной среде происходит событие onScroll, информация асинхронно передаётся в JavaScript-окружение. Но нативные механизмы не ждут до тех пор, пока JavaScript-часть приложения сделает своё дело и им об этом отчитается. Из-за этого возникает задержка, выражающаяся в появлении в списке, перед выводом его содержимого, пустого пространства.
Аналогично, перед тем, как результаты пересчёта макета дойдут до экрана, системе предстоит выполнить несколько сеансов обмена данными. Так, прежде чем данные макета попадут в нативную среду, их нужно обработать в Yoga. А это, понятно, означает, что такие данные тоже приходится передавать через мост.
Как видно, отправка JSON-данных с использованием асинхронных механизмов, а также сериализация и десериализация этих данных, вызывают проблемы с производительностью. Этот механизм несовершенен. Но как ещё можно наладить взаимодействие между нативными механизмами и JavaScript? Ответить на этот вопрос можно с помощью технологии JSI.
Новая архитектура
В ходе перепроектирования архитектуры React Native выполняется постепенный отказ от функций моста, на смену которым приходит новый механизм, называемый JavaScript Interface (JSI).
Применение JSI открывает возможности для некоторых замечательных улучшений.
Первое такое улучшение заключается в том, что JS-бандл больше не полагается на JSC. Другими словами, движок JSC теперь легко можно заменить на что-то другое, вполне возможно, отличающееся более высокой производительность. Например — на движок V8.
Второе улучшение — это то, что лежит в основе новой архитектуры React Native. Оно заключается в том, что, благодаря использованию JSI, в JavaScript можно хранить ссылки на C++-объекты (Host Objects) и вызывать методы этих объектов. В результате оказывается, что JavaScript-часть приложения и нативные механизмы будут знать друг о друге гораздо больше, чем раньше.
Иными словами, JSI позволяет обеспечить полное взаимодействие между всеми потоками. Благодаря использованию концепции совместного владения (shared ownership), JavaScript-код может запускать нативные методы непосредственно из JS-потока. При этом нет необходимости в том, чтобы использовать JSON для передачи данных. Это позволяет избавиться от проблем, характерных для использования моста, связанных с возможным переполнением очереди и с асинхронной передачей данных.
Новая архитектура React Native (источник)
В дополнение к тому, что вышеописанная схема значительно улучшает взаимодействие между различными потоками, новая архитектура, кроме того, позволяет осуществлять непосредственное управление нативными модулями. Это значит, что нативные модули можно использовать тогда, тогда они нужны, а не инициализировать их все при запуске приложения. Это приводит к серьёзному увеличению скорости запуска приложений.
Этот новый механизм может открыть дорогу к разработке новых проектов, способных на то, что было недоступно старым RN-приложениям. Дело в том, что теперь в нашем распоряжении оказалась мощь C++. А это значит, что на базе React Native теперь можно будет создавать гораздо больше разновидностей приложений, чем раньше.
Итоги
За годы развития в инфраструктуре React Native накопилось много такого, что сегодня можно признать устаревшим и неиспользуемым. Стремление разработчиков RN к тому, чтобы убрать из него ненужное и улучшить возможности по его поддержке, ведёт к удалению некоторых его возможностей. Например, речь идёт о том, что модули его ядра, вроде Webview и AsyncStorage, постепенно выводятся из состава фреймворка и оформляются в виде репозиториев, развиваемых силами сообщества React Native.
Благодаря новому компактному ядру и впечатляющим механизмам взаимодействия между JavaScript-кодом и нативным кодом, перепроектирование архитектуры React Native должно привести к тому, что RN-приложения станут производительнее, и к тому, что их будет быстрее и удобнее разрабатывать.
Ожидается, что реструктуризация React Native завершится в 4 квартале 2020 года. Меня восхищают те перспективы, касающиеся производительности приложений и удобства разработки, которые открываются благодаря переработке архитектуры RN.
Если вы пользуетесь React Native — расскажите о том, с какими недостатками этого фреймворка вам довелось столкнуться.
Комментариев нет:
Отправить комментарий