Паодин Цзе Ню! Получите глубокое понимание рефакторинга архитектуры нового поколения React Native.

React Native
Паодин Цзе Ню! Получите глубокое понимание рефакторинга архитектуры нового поколения React Native.

Обзор

В июне 2018 года Facebook официально объявил о плане масштабного рефакторинга React Native и дорожной карте рефакторинга. Цель состоит в том, чтобы сделать React Native более легким, более подходящим для смешанной разработки и приблизиться к нативному опыту или даже достичь его. (Возможно также, что команда React Native почувствовала нагоняющее давление Google Flutter и должна внести серьезные изменения в архитектуру, прежде чем в будущем можно будет полноценно конкурировать с Flutter). Судя по официальной информации Facebook, это революционный рефакторинг архитектуры, основное содержание рефакторинга выглядит следующим образом:

  1. Изменить режим потока. Обновления пользовательского интерфейса больше не нужно запускать в трех разных потоках одновременно. Вместо этого JavaScript можно вызывать синхронно в любом потоке для определения приоритета обновлений, в то же время выталкивая низкоприоритетную работу из основного потока, чтобы поддерживать отзывчивость пользовательского интерфейса.
  2. Внедрите возможности асинхронного рендеринга. Позволяет выполнять несколько рендеров и упрощает асинхронную обработку данных.
  3. Упростите JSbridge, пусть он будет быстрее, легче.

1. Какие текущие проблемы с React Native?

В настоящее время фреймворк React Native в индустрии получил широкое распространение. JD.com начал свою деятельность относительно рано в этом отношении, и его общее решение является относительно зрелым. В настоящее время глубоко адаптированное и расширенное решение JDReact от JDReact накопило доступ к более чем 200 службам RN и более чем 20 независимым приложениям и обеспечило десятки миллионов DAU. От фактического развития бизнеса еще много ям, среди которых более очевидны проблемы с производительностью, а конкретные проблемы заключаются в следующем: Производительность загрузки низкая, поскольку системные или настраиваемые собственные компоненты пользовательского интерфейса, а также процесс регистрации и загрузки API должны проверять согласованность всех атрибутов и JS API, что влияет на производительность загрузки и даже напрямую приводит к тому, что основной поток пользовательского интерфейса легко блокируется. . JSBridge, общий жизненный цикл React Native слишком тесно связан с JSBridge.Все операции между нативным и JS проходят через этот мост, и каждое сообщение о событии имеет временной интервал, что приводит к асинхронности всего процесса рендеринга. Проблема с жестами, текущая архитектура React Native, многие сложные проблемы с жестами трудно решить со стороны JS, и для решения проблемы требуется перенастройка SDK. Обработка событий возврата, текущие события возврата не могут быть прослушаны в компонентах, подобных нативным. Расчет макета, общий расчет пользовательского интерфейса должен быть выполнен в теневой компоновке, расчет в общей структуре платформы невозможен.

Ниже перечислены существующие компоненты Native & JS Component, с помощью которых можно выполнять собственный рендеринг пользовательского интерфейса и вызовы API. Эти компоненты регистрируются в системе через диспетчер пакетов.При запуске службы RN требуются некоторые проверки общих атрибутов и методов, что приводит к потере производительности.Кроме того, RN позволяет одновременно регистрировать несколько диспетчеров пакетов, когда количество API слишком велико. , возникающую проблему необходимо обходить в цикле, а вызывающий процесс также имеет потерю производительности.

  1. Нативные модули, собственный интерфейс API.
  2. ViewManager, собственный компонент пользовательского интерфейса.
  3. Родная навигация, собственный компонент навигации.
  4. ComponentKit и Litho, изначально основанные на компонентах пользовательского интерфейса йоги.
  5. RCTSurface, собственная реализация Surface. В процессе загрузки сначала необходимо загрузить и инициализировать React Native Core Bridge, включая некоторые из вышеперечисленных функций компонентов, а затем можно запустить код JS для бизнеса.Только после завершения этого шага React Native может отображать Компоненты JS в нативные компоненты:

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

2. Как решить эти проблемы полностью?

В ответ на некоторые проблемы с существующим фреймворком Facebook предпринял большую работу по оптимизации в последней версии, которая была обновлена ​​до версии 0.58 с момента ее выпуска в 2013 году, а в прошлом году было выпущено более 10 версий. Как видно из обновления версии, в дополнение к некоторым обновлениям компонентов и исправлениям ошибок Facebook предпринял попытку оптимизировать производительность, чтобы сделать загрузку и производительность рендеринга максимально естественной.

Версия с оптимизированной производительностью: 0.33 Ленивый модуль 0,40 ОЗУ связка/развязка 0.43 FlatList/SectionList/VirtualizedList 0,50 SwipeableFlatList/Fiber

Ниже приведены некоторые официально рекомендуемые в настоящее время решения для оптимизации производительности:

  1. Для отложенной регистрации загрузки компонентов собственная сторона может использовать отложенную регистрацию для регистрации, когда бизнес использует компонент.
  2. Упаковка по запросу, прямое уменьшение размера бизнес-пакета, удаление некоторых ненужных модулей и повышение скорости рендеринга.
  3. Ленивая загрузка бизнеса напрямую сокращает время, необходимое для каждого компонента в процессе бизнес-рендеринга.
  4. UNBundle, который разбивает бизнес на небольшие модули, обеспечивает производительность.
  5. Удалите ненужные модули модуля JS во время инициализации.
  6. Предоставьте готовые инструменты для оптимизации кода JS. Последняя архитектура также предлагает архитектуру Fibe/Relay Modern.Общая производительность рендеринга была значительно улучшена по сравнению с прошлым.Последний SDK JDReact был обновлен до этой архитектуры.Цель состоит в том, чтобы свести к минимуму накладные расходы на загрузку JSBridge, но статья упоминалось ранее. Проблема узких мест еще не решена. Мы сравнили запуск и отрисовку межплатформенного фреймворка Flutter, React Native немного лучше Flutter по производительности запуска, но явно уступает Flutter по части отрисовки, что мы и называем проблемой узкого места. сравнение следующее:

Поэтому наш вывод: различные оптимизации под существующую архитектуру сложно полностью решить проблему производительности.

3. Только архитектурный рефакторинг имеет решающее значение

На недавней конференции разработчиков Facebook представил ход рефакторинга архитектуры следующего поколения. Мы также проанализировали часть исходного кода, представленного в основной ветке, и можем понять некоторые проекты прототипов новой архитектуры. Общая архитектура все еще продолжается. В оптимизации, думаю, сюрпризов будет больше. Судя по имеющейся информации и коду, влияние бизнес-уровня JS невелико, и после такого крупномасштабного рефакторинга архитектуры не потребуется адаптировать много бизнес-кода. Этот рефакторинг в основном касается рефакторинга JSBridge и его собственной архитектуры.Давайте сравним и представим общую структуру на нескольких уровнях:

3.1 Принцип рендеринга существующей архитектуры

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

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

Давайте рассмотрим пример SrollView, который является компонентом с наибольшей производительностью и проблемами, о которых сообщает бизнес или сообщество. Исходная версия ScollView визуализируется один раз без какой-либо повторной обработки, поэтому производительность при запуске низкая, а использование памяти велико. Последующие версии Flatlist имеют восстановленные компоненты, и память в основном стабильна, но возникают проблемы во время быстрого процесса скольжения, который легко вызывает белый экран и легко зависает. Если вы посмотрите на блок-схему ниже, вы поймете, почему Flatlist (на основе ScollView)/ScrollView будет иметь длинный белый экран или зависать при быстром скольжении.

Во время быстрого скользящего процесса Flatlist уровень JS будет запускать рендеринг элемента Flatlist для рендеринга каждого фрагмента данных в соответствии с событием скольжения, но из-за асинхронного отношения JSBridge окончательный рендеринг теневого слоя в собственный Пользовательский интерфейс асинхронный, и скольжение будет слишком быстрым.Большое количество событий пользовательского интерфейса будет блокироваться в JSBridge, что также приводит к появлению длинного белого экрана.В то же время, когда некоторые элементы выскальзывают из определенного диапазона видимого область, содержимое пользовательского интерфейса будет переработано и повторно отрисовано после следующего слайда в область.

3.2 Принцип рендеринга новой архитектуры Fabric

Процесс рендеринга пользовательского интерфейса разделен на три уровня, как и ранее представленная архитектура: бизнес-уровень JS, теневое дерево и собственный уровень пользовательского интерфейса. Отделяет чрезмерную зависимость от JSBridge от уровня JS к теневому уровню, в котором JS и теневое дерево синхронизируют данные через новую архитектуру JSI (принцип будет представлен позже), что может обеспечить синхронное обновление одного компонента узла, поэтому который JS отображает в Native, может использовать синхронную операцию или асинхронную операцию. В то же время, поскольку теневой слой и слой JS находятся во взаимном соответствии, они могут обеспечить более точное управление пользовательским интерфейсом. Приблизительная схема выглядит следующим образом. :

Возвращаясь к предыдущему примеру ScrollView, давайте посмотрим, как Fabric решает проблему производительности при быстрой прокрутке.

  1. Инициализация: уровень JS to Shadow уже является синхронной операцией, а уровень Shadow для собственного пользовательского интерфейса может работать асинхронно или синхронно.Компоненты могут адаптироваться к различным операциям в соответствии со своими бизнес-сценариями.

  2. Процесс скольжения: нативная сторона напрямую управляет рендерингом слоя JS и одновременно создает узел узла теневого слоя.Узел узла будет отображать собственный пользовательский интерфейс синхронно.Визуализация скольжения в целом процесс является синхронной операцией, поэтому под старой архитектурой не будет белых пятен проблема с экраном.

3.3 Fabric — новая архитектура пользовательского интерфейса

  1. React Fabric Renderer (JS), архитектура рендеринга на стороне JS.
  2. FabricUIManager (JS, C++), модули управления пользовательским интерфейсом на стороне JS и на стороне.
  3. ComponentDescriptor (C++), уникальное описание собственного компонента и определение свойств компонента.
  4. Внедрение компонента для конкретной платформы (ObjC++, Java), собственный компонент.
  5. RCTSurface (ObjC++, Java), компонент Surface.

Судя по структурному описанию этих компонентов, новая архитектура Fabric выглядит примерно так:

  1. Теневой слой перемещается из исходного слоя Java в слой C++.
  2. Уровень C++ управляет всеми компонентами пользовательского интерфейса.Исходный UIManager уровня Java заменяется уровнем C++, и эти уровни C++ управляются виртуальными компонентами.
  3. Собственные компоненты будут генерировать соответствующие экземпляры на уровне C++ через уровень JNI и связывать некоторые свойства и методы.
  4. Уровень JS FabricUIManager вызывает уровень C++ для создания узлов узлов через JSI и, наконец, соответствует нашему дескриптору компонента. С общей точки зрения узлы узлов на стороне JS могут полностью соответствовать узлам узлов на стороне C++.Через JSI можно выполнять синхронные вызовы и синхронизацию атрибутов.Точно так же от C++ к нативному уровню java к компонентам выполняется через JNI, а также синхронизируется.

Ниже мы ссылаемся на часть кода, который в настоящее время открыт Facebook:

  1. ComponentDescriptor, уровень абстракции, соответствующий нативному и нативному пользовательскому интерфейсу, реализует свойства и события нативных компонентов и регистрирует их в commonRegister с помощью уникального идентификатора.Ниже представлена ​​структура кода открытого компонента-переключателя.

Как устанавливаются общие компоненты Fabric UIManger и каналы сообщений? Вы можете обратиться к файлу Scheduler.cpp, JS вызовет этот интерфейс через JSI для инициализации.

  1. Регистрация компонентов ткани.
  2. Регистрация канала сообщений.
  3. Инициализируйте UIManager и UIManagerbinding, среди которых UIManager предоставляет такие функции, как создание узла, клонирование узла, добавление теневого узла, закрытие поверхности и т. д. UIManagerbinding основан на интерфейсе JSI и напрямую реализует прямой вызов с помощью JS UIManger. Вы можете обратиться к исходному коду Сторона JS осуществляется через JSI. Метод get уведомляет UIManagerbinding о выполнении UIManger уровня C++ через атрибуты, и UImanger в конечном итоге сгенерирует соответствующий пользовательский интерфейс в соответствии с сгенерированным теневым узлом.

Давайте посмотрим, как на стороне JS создаются нативные компоненты. Вы можете сравнить исходный код. На стороне JS у нас есть FabricUIManager. В процессе инициализации UIManagerBinding он регистрируется в работающей среде JS. Поскольку UIManagerBinding реализуется JSI, его можно понимать как us. Объект Host proxy создается и регистрируется в JS, и сторона JS также соответствует одной и той же структуре данных по очереди.

Вот пример создания узла:

Исходя из текущей структуры, последующую разработку пользовательского интерфейса Fabric необходимо разрабатывать на трех уровнях: компонентный слой C++, теневой слой и собственный уровень Java, а созданный теневой слой также находится во взаимно однозначном соответствии с узлами узлов слой JS через JSI.

3.4 Введение в JSI

JSI упоминался при представлении архитектуры Fabric выше, так что же такое JSI? Как мы можем добиться более атомарного контроля над каждым модулем и API? Он представляет собой мост между JS и родной Java или Objc, похожий на JSBridge существующей архитектуры, но использующий метод совместного использования памяти и класс прокси.Все рабочие среды JSI находятся в среде JSCRuntime, чтобы достичь и JS For прямая связь между терминалами, нам нужно реализовать JSI::HostObject на уровне C++.Эта структура данных имеет только два интерфейса, get и set.Вызовы разных интерфейсов различаются реквизитом.

Затем сгенерируйте JSObject через интерфейс JSI, вы можете видеть, что сгенерированный прокси-объект и наш HostObjectProxy совместно используют память, а методы set и get также реализованы в прокси. Ниже приведен конкретный процесс:

Соответствующий LazyObject на стороне JS должен завершить вызов метода hostobject, соответствующего реализации C++, посредством установки и получения этих объектов:

3.5 Архитектура турбомодуля

Это архитектура собственного модуля, основанная на новой архитектуре JSI. Уровень JS получает прокси объекта модуля соответствующего уровня C++ через JSI и, наконец, вызывает модуль уровня Java через JNI. NativeMoudleProxy уровня C++ — это объект, реализованный JSI.Вы можете передать имя модуля, чтобы получить модуль, зарегистрированный уровнем C++, и все имена методов API, инкапсулированные этим модулем. Поэтому, когда служба JS загружена, прокси-сервер создаст прокси-объект объекта JS для слоя JS.

2. Уровень JS передает реквизит JSI через API getNativeModule и, наконец, находит объект Host NativeModuleProxy через интерфейс JSI, поскольку основная функция NativeModuleProxy заключается в создании вызова уровня JS, переданного объектом JS через имя объекта. Модуль JSI зарегистрирован на уровне C++, поэтому в его методе get есть только один атрибут, который заключается в получении соответствующего модуля через JSINativeModules, а JSINativeModules имеет механизм кеша.Если кеша нет, все API в модуле будут напрямую анализируется, и если есть кэшированный модуль, информация читается напрямую.

Вы можете видеть, что в процессе парсинга в новой версии добавлены синхронные и асинхронные методы, а именно promise и sync. Таким образом, модуль JSI может фактически управлять API синхронно, в отличие от предыдущих API JSBridge, которые работают асинхронно.Преимущество синхронной работы заключается в том, что она может обеспечить синхронизацию между потоками.

Все модули JSI зарегистрированы в JSIMOMDLEGISTRY.Конечно, зарегистрированный уровень C++ MOULDE, и все модули C++, наконец, связывают модуль уровня Java, зарегистрированный в Turbomoudle уровня Java, через дескриптор, то есть API, который мы наконец, реализован в исходном конце, поэтому модуль уровня C ++ будет запускать уровень Java, запуская уровень Java с помощью соответствующего свойства метода.

Собственный модуль уровня C++ регистрируется в процессе инициализации диспетчера экземпляров уровня java, проходя и регистрируя уровень java и модуль C++.

Все вызовы для понимания собственного модуля на самом деле являются вызовами JSI, а возвращаемым результатом операции является базовый тип данных или объект JSI, поэтому при вызове метода турбо-модуля возвращаемое значение может быть объектом JSI. Разработчики могут инкапсулировать некоторые полные структуры данных в хост-объект JSI в соответствии со своими бизнес-потребностями, чтобы сторона JS также могла получить объект и сформировать прокси-отношения с объектом нативной стороны, который может синхронно завершить серию объектов. функция porp работает, например: В предыдущем методе вызова изображение было получено через JSBridge, этот тип данных может соответствовать только некоторым базовым типам данных на стороне JS, таким как тип String адреса изображения, на который мы ссылаемся, поэтому, если мы хотим загрузить это изображение теперь мы загрузим данные на стороне JS, а затем отправим их обратно на нативную сторону, как показано ниже:

Но с JSI все по-другому.То, что мы получаем на стороне JS через JSI, это JS Object, то есть картинка, но картинка уже не простой тип данных, а структура, которая образует связывающую связь с нативным side., может поддерживать синхронную установку многих свойств, таких как изменение альфа-значения и т. д., что будет напрямую запускать свойства и вызовы функций хост-объекта, поэтому нам больше не нужно изменять альфа-канал, как раньше, что требует много вызовов JSBridge, и процесс загрузки может управляться напрямую.Объект уровня c++ выполняет операцию загрузки.

Ниже приводится краткий список взаимосвязей вызова на соответствующих уровнях:

3.6 Сообщество

React Native в настоящее время имеет 52 напрямую зависимых пакета, а затем эти пакеты косвенно и рекурсивно зависят от пакетов 589. Вы можете проверить общие зависимости на веб-сайте http://npm.anvaka.com/#/view/2d/react-native, очень сложный граф. Судя по текущему открытому исходному коду React Native, весь проект представляет собой большой репозиторий, включающий множество компонентов Native, API и модулей JS.Как упростить обслуживание этих компонентов и API и сделать доступ к Упрощение архитектуры React Native Способность APP быстро сокращать API или компоненты также стала целью рефакторинга этой архитектуры. Удалите некоторые ненужные модули и разделите все модули на небольшие репозитории, аналогичные модулям, состоящим из компонентов. Уменьшите размер кода пакета для развития бизнеса (внедряйте и компилируйте необходимые компоненты по запросу), тем самым повышая скорость рендеринга и запуска бизнеса. После разделения эти компоненты или API-интерфейсы открываются для сообщества, и сообщество может предоставлять свои собственные ресурсы, что может ускорить обслуживание и итерацию компонентов и помочь снизить стоимость обслуживания компонентов. Уменьшите зависимость текущей разработки React Native SDK, упростите структуру кода и упростите и ускорьте обновление SDK. Увеличение вклада сообщества ускоряет исправление PR, позволяет большему количеству разработчиков сосредоточиться на более разумном месте, сокращает повторяющуюся разработку и снижает сложность открытия. Планы на будущее в фейсбуке:

  1. Удалите все компоненты или API, которые, по вашему мнению, недостаточно используются или бесполезны.
  2. Переместите существующие поддерживаемые модули во внешнее репо, поддерживаемое отдельно. Для планирования и классификации конкретных компонентов вы можете обратиться к списку, предоставленному Facebook.Многие компоненты были явно удалены или перемещены в сообщество с открытым исходным кодом, например WebView:docs.Google.com/spreadsheet…Вот дорожная карта для сообщества компонентов React Native:

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

  1. Все компоненты не могут быть синхронизированы одновременно, как раньше, разработчикам необходимо устанавливать репозиторий по запросу в соответствии с раздельным репо, а фрагментация серьезная.
  2. Поскольку вклад разработчиков будет все быстрее и быстрее после работы сообщества, а итерация версий будет ускорена, важными вопросами также являются контроль версий и безопасность кода.
  3. Многие компоненты, основанные на сообществе, зависят от собственных модулей, что усложняет интегрированную разработку для фронтенд-разработчиков.

4. Резюме

Мы кратко представили дизайн платформы Facebook React Native следующего поколения с помощью анализа исходного кода и считаем, что будут большие изменения с точки зрения производительности и функций. Хотя общие изменения велики, для фронтенд-разработчиков изменения в JS минимальны, и основное внимание уделяется нативным компонентам и архитектуре API.Инкапсуляция становится более сложной, и необходимо инкапсулировать теневой слой C++, поэтому предыдущая разработка JAVA была расширена до C ++. И разработка JAVA, структура знаний разработчика и требования к резервам выше, но для повышения производительности это того стоит. После работы на базе сообщества официальные лица Facebook могут разрабатывать вместе с предыдущими компонентами и фреймворками, упрощая его, чтобы сосредоточиться только на общих возможностях и производительности фреймворка, позволяя разработчикам вносить свой вклад и поддерживать текущие компоненты, что значительно улучшает итерационный цикл фреймворк. Техническая команда JD по многотерминальному слиянию также будет продолжать уделять внимание.После того, как новая архитектура React Native станет стабильной, будет обновлен механизм рендеринга и представлены новые архитектуры и функции. В будущем он также откроет нижний уровень и компоненты двойных движков JDReact и JDFlutter, чтобы обеспечить более комплексное кросс-конечное решение.