Как автор серии открытых материалов GSY, в прошлом году я также подготовил сравнительную статью под названием «Глубинный анализ кросс-платформенного мобильного разработки». Через год после этого, данная статья будет полностью анализировать и сравнивать текущие версии React Native и Flutter, рассматривая семь аспектов: установка окружения, реализация принципов, программирование, разработка плагинов, компиляция и выполнение, производительность и стабильность, будущее развитие. Надеюсь, что это поможет вам получить более ценное представление.
Да, на этот раз нет Weex, предупреждение о большом объеме информации, рекомендую сохранить и прочитать позже.
Под зимним холодом, кросс-платформенная мобильная разработка, пройдя через несколько лет взлётов и падений, теперь снова находится под светом прожектора. Осталось всего два игрока — React Native и Flutter. Как «ветераны», которым уже несколько лет, и «новички», которые стали популярными в 2019 году, они продолжают соперничество, которое интересует многих разработчиков.> Когда меня спрашивали: "Он пишет как на Java, так и на Objective-C, может работать одновременно на платформах Android и iOS, почему ему стоит учиться кросс-платформенной разработке?",
мой ответ был таким: преимущества кросс-платформенной разработки не заключаются в производительности или стоимости обучения; даже адаптация к платформам может занять больше времени. Однако она позволяет логике кода (особенно бизнес-логике) быть использованной без изменений на всех платформах, снижает затраты на обслуживание повторяющегося кода и обеспечивает единое представление на всех платформах, если при этом можно гарантировать хорошую производительность, то это будет идеальным решением.## I. Установка окружения
Независимо от того, используется ли React Native или Flutter, требуется среда разработки для Android и iOS, такие как JDK, Android SDK, Xcode и т.д., но различия заключаются в следующем:
node_modules
и сложностью зависимостей, в примерах, с которыми мне пришлось столкнуться, первый запуск Flutter обычно успешнее, чем React Native, причём основные причины неудач при первом запуске Flutter связаны с сетью.При этом для кросс-платформенного развития рекомендуются Mac, причины этому отсутствуют.## II. Реализация принципов
По умолчанию на платформах Android и iOS Flutter и React Native требуют наличия нативного компонента Activity
/ ViewController
, который является "одностраничным приложением". Основное различие между ними заключается в том, как они строят пользовательский интерфейс:
React Native — это набор фреймворков для создания пользовательского интерфейса. По умолчанию React Native загружает файлы JavaScript в Activity
, а затем выполняет их в JavaScriptCore
. После этого он парсит файлы бандла и создаёт стэк из нативных компонентов для отображения.
Проще говоря, React Native позволяет конфигурировать страницы через написание кода на JavaScript, после чего эти данные будут распарсены и отрендерены в виде нативных компонентов. Например, тег <View>
соответствует ViewGroup/UIView
, <ScrollView>
— ScrollView/UIScrollView
, <Image>
— ImageView/UIImageView
.
По сравнению с другими фреймворками, такими как Ionic
, React Native обеспечивает более высокую производительность страниц.
Если React Native делает работу по совместимости платформ для разработчиков, то Flutter больше скрывает понятие платформы.
Flutter требует лишь предоставления поверхности (
Surface
) и холста (Canvas
). Все остальное Flutter берёт на себя: "вы можете просто лежать, мы всё сделаем".
widgets
в Flutter не зависят от платформы. Разработчики используют фреймворк для создания приложений, а сам фреймворк работает на движке (engine
), который адаптируется и поддерживает кросс-платформенные возможности. Этот процесс поддержки кросс-платформенных возможностей сводится к тому, что Flutter UI преобразует свои widgets
в цифровое состояние, которое затем рендерится на экран через Skia
движка.Из всего вышеописанного можно сделать вывод, что идеология React Native "Учиться один раз, писать везде" означает, что если вы знаете React, то можете использовать этот опыт для создания эффективного приложения; в то время как Flutter предлагает забыть о платформе и сосредоточиться на создании интерфейсов Flutter UI. - DOM:
Кроме того, известна концепция виртуального DOM, используемого в React, которая обеспечивает одну из гарантий производительности React. Аналогичная идея виртуального DOM также присутствует в Flutter.
Те, кто читал мои статьи о Flutter, могут знать, что те
widgets
, которые мы пишем в Flutter, фактически не являются настоящими компонентами отрисовки. Это схоже с тем, как в React Native используются теги.widget
больше похож на конфигурационный файл, а деревоwidgets
не является реальным деревом отрисовки.При рендереWidget
проходит через измененияElement
, чтобы затем преобразовать его вRenderObject
для отрисовки. Только деревоRenderObject
, которое в итоге создается, можно считать "настоящим DOM отрисовки". Каждое изменение дереваWidget
не обязательно приведет к полной перезагрузке дереваRenderObject
.
Поэтому принцип реализации в React Native и Flutter полностью различаются, хотя оба имеют аналогичную концепцию "виртуального DOM". Однако React Native имеет более сильную связь с платформой, в то время как связь Flutter с платформой очень слаба.
React Native использует язык JavaScript, который всем хорошо известен. Языку уже 24 года, и он активно используется во всех платформах и приложениях. После того как React стал популярен благодаря Facebook, React Native был выпущен в 2015 году, предоставляя возможность расширения навыков JavaScript-разработчикам.
Flutter использует язык Dart, который был создан в 2011 году и достиг версии 2.0 в 2018 году. Начальный выпуск этого языка был направлен против JavaScript, но он долгое время не пользовался популярностью в Web-среде до тех пор, пока Flutter не начал набирать обороты в 2017 году. После этого Flutter For Web продолжил попытки вернуться в область Web.Разработка программного обеспечения включает множество аспектов. Далее будут рассмотрены четыре основных аспекта сравнения: programming language
, user interface
, state management
, initial controllers
.> Одним из самых часто задаваемых вопросов является почему Flutter-команда выбрала Dart, а не JS. Было высказано мнение, что это связано с близостью команд Dart и Flutter, а также с желанием Google избежать связи с продукцией Oracle. В то же время React Native обновляется уже почти 4 года, но номер версии все ещё не превышает 1.0.
Изначально оба языка были созданы для работы в Web, поэтому они имеют много общего.
Как показывают следующие примеры кода, оба поддерживают определение переменных с помощью var
, async/await
синтаксис, Promise
(Future
) для цепочки асинхронных операций, а также синтаксис *
/ yield
(хотя этот последний пример может быть неточным), что указывает на их родственные отношения.```markdown
var a = 1;
async function doSomething() {
var result = await xxxx();
doAsync().then((res) => {
console.log("ffff");
});
}
function* _loadUserInfo() {
console.log("**********************");
yield put(UpdateUserAction(res.data));
}
var a = 1;
void doSomething() async {
var result = await xxxx();
doAsync().then((res) {
print('ffff');
});
}
_loadUserInfo() async* {
print("**********************");
yield UpdateUserAction(res.data);
}
Однако между ними есть много различий, а самое главное отличие заключается в том, что JavaScript — это динамический язык, а Dart — псевдо-динамический язык с жёсткими типами данных.
Например, в следующем коде:- В Dart можно прямо указывать тип переменной name
, как String
.
otherName
, объявленная через var
, будет иметь тип, который будет автоматически выведен компилятором при присвоении значения.dynamicName
, объявленная как dynamic
, действительно является динамической переменной, которая проверяет тип во время выполнения.```dart
// DartString name = 'dart'; var otherName = 'Dart'; dynamic dynamicName = 'dynamic Dart';
Наиболее наглядно это видно на примере ниже:
- Когда `var i` объявлено глобально без явного типа, оно становится `dynamic`, и компилятор не проверяет его тип при компиляции метода `init()`. Это аналогично поведению в JavaScript.
- Если же `var i = "";` объявлено внутри метода `init()`, то `i` становится строго типизированной переменной типа `String`, и попытка увеличить её значение (`i++`) вызовет ошибку компиляции. Однако такой код не вызвал бы ошибок при компиляции в JavaScript.

##### Динамические и статические языки имеют свои преимущества и недостатки. Например, JavaScript значительно проще и удобнее в использовании по сравнению с Dart, но Dart обеспечивает более высокую безопасность типов данных и устойчивость при рефакторинге кода.
#### 3.2 Интерфейсное программирование
**React Native** продолжает использовать стиль разработки, характерный для React, поддерживает SCSS/SASS, разделение стилей от логики, начиная с версии 0.59 поддерживает функциональное программирование с помощью React Hooks и т.д., однако меняются метки и некоторые свойства и стили адаптированы для совместимости с платформой.На следующем рисунке показана общая реализация обычного компонента React Native: **наследование класса Component, передача параметров через props, возврат нужного макета в методе render, установка стилей для каждого контроля через style** и т.д. Для фронтенд-разработчиков это практически не требует обучения.```plaintext
[Рисунок]

**Flutter** одним из своих наиболее значимых преимуществ имеет то, что это платформенно-независимый UI-фреймворк, где все в Flutter — это `Widget`.
Как показано на следующем рисунке, в разработке **Flutter** обычно используются два типа компонентов: **безсостоятельные `StatelessWidget`** и **состоятельные `StatefulWidget`**, чтобы создать страницы. Затем внутри метода **`Widget build(BuildContext context)`** реализуется макет с помощью различных `Widget`, используя свойства `child` или `children` для вложения одного `Widget` в другой и передачей аргументов через конструкторы компонентов. В конце каждого компонента устанавливаются стили.
Что касается разработки компонентов **Flutter**, самыми распространенными жалобами являются **вложение компонентов и отсутствие разделения кода стилей**. Проблема разделения кода стилей требует реального опыта разработки для полной оценки, но относительно вложений можно сделать некоторые замечания:
**Flutter** полностью следует принципу «все есть `Widget`», поэтому **размер части `Widget` очень мал**, такие как `Padding` и `Center` — это отдельные `Widget`. Даже **общие состояния реализуются через `InheritedWidget`**, что является причиной сложности вложения и непривлекательности кода стилей.
```**На самом деле благодаря этому маленькому размеру части `Widget`, вы можете свободно комбинировать различные `Widget` для создания множества бизнес-шаблонов,** таких как часто используемый в Flutter `Container`, который представляет собой один из шаблонов, предоставленных официальной командой. **`Container` состоит из нескольких компонентов, таких как `Align`, `ConstrainedBox`, `DecoratedBox`, `Padding`, `Transform` и других**, поэтому глубину вложений можно контролировать самостоятельно, а также достигать более детализированного контроля над производительностью и отрисовкой.
Конечно, **официальная команда постоянно работает над улучшением опыта разработки и визуализации**, как показано на следующем рисунке, будущее развитие должно ещё больше улучшить эту ситуацию.
Конечно, давайте сделаем вывод, отбросив упомянутый выше стиль разработки. **Основной особенностью UI-разработки в React Native является платформозависимость, в то время как Flutter ориентирован на платформонезависимость. Например, при реализации функции прокрутки вниз для обновления данных, компонент `<RefreshControl>` в React Native обеспечивает различные эффекты прокрутки в зависимости от используемой платформы. В Flutter же, чтобы получить различия в эффектах прокрутки вниз для разных платформ, вам потребуется использовать разные компоненты — `RefreshIndicator` для Android и `CupertinoSliverRefreshControl` для iOS; в противном случае, все платформы будут отображать одинаковый эффект.**#### 3.3 Управление состояниемРанее уже упоминалось, что **Flutter** внесло множество заимствований из **React Native**, поэтому управление состоянием также выглядит знакомо — обновления происходят путём вызова метода `setState`, при этом изменения не применяются немедленно. Однако между ними есть различия, как показано ниже:
- В обычной ситуации **React Native** требует инициализации переменной `this.state` внутри компонента `Component`, а затем доступ к значению осуществляется через `this.state.name`.
- **Flutter** наследует от `StatefulWidget`, после чего прямым образом использует переменные внутри объекта `State` и вызывает `setState` для обновления.
```dart
/// JS
this.state = {
name: ""
};
···
this.setState({
name: "loading"
});
···
<Text>this.state.name</Text>
/// Dart
var name = "";
setState(() {
name = "loading";
});
···
Text(name)
Конечно, внутренняя реализация этих двух систем имеет существенные отличия, например, React Native зависит от механизма сравнения React diff, тогда как Flutter подвержена влиянию таких свойств, как isRepaintBoundary
и markNeedsBuild
.
Что касается сторонних решений для управления состоянием, то они демонстрируют высокую степень схожести. Например, на ранней стадии развития платформы Flutter появились различные фреймворки управления состоянием, такие как: flutter_redux, fish_redux, dva_flutter, flutter_mobx и многие другие, все они имеют характерные черты React.При этом Flutter предоставляет официально такие решения для управления состоянием, как scoped_model и provider, которые учитывают специфику платформы Flutter.
Поэтому можно сказать, что управление состоянием в React Native и Flutter очень похоже, если не сказать, что Flutter следует за React.
В процессе кросс-платформенного программирования нельзя обойтись без использования поддержки существующих платформ, например, включением браузера x5 на платформе Android, внедрением модулей воспроизведения видео, анимации Lottie и других. Поддержка анимации React Native встроена изначально, а в сообществе уже есть проекты, аналогичные lottie-react-native. Поскольку весь процесс рендера выполняется на нативном уровне, интеграция существующих платформенных компонентов не представляет особой сложности. В то же время, благодаря многолетнему развитию, хотя качество различных сторонних библиотек может отличаться, их количество является значительным преимуществом. И Flutter в этом случае явно уступает, даже официальная версия сначала не поддерживала WebView
, что связано с принципами реализации Flutter.Потому что рендеринг всего приложения Flutter полностью отвязан от нативной среды и работает напрямую с GPU, что делает невозможным прямое использование нативных компонентов. Видеоплееры же были реализованы через внешние текстуры, но этот процесс требует преобразования данных, что ограничивает его универсальность. Поэтому в последующих версиях Flutter предоставил режим PlatformView
для интеграции.> Например, на Android Flutter использует Presentation
для отображения второго экрана, а также VirtualDisplay
для рисования нативных компонентов в памяти на уровне Surface
. VirtualDisplay
рисуется на Surface
с помощью textureId, после чего информация передается на уровень Dart, где используется AndroidView
с заранее определенным Widget
и textureId. Таким образом, Engine рисует данные, связанные с textureId, прямо в памяти и выводит их на AndroidView
.
Разработка PlatformView
неизбежно привела к снижению производительности, что проявилось в увеличении потребления памяти, а также вызвала проблемы, такие как невозможность открытия клавиатуры #19718 и черный экран. Иногда производительность на Android может оказаться хуже, чем при использовании внешних текстур.
На данный момент интеграция нативных компонентов в Flutter всё ещё менее надёжна, чем в React Native.
React Native и Flutter поддерживают разработку плагинов, но различаются подходами к этому вопросу: React Native создаёт плагины для npm, а Flutter — для pub.
Преимуществом использования npm-плагинов в React Native является доступ к богатой экосистеме npm и снижение затрат времени на обучение для фронтенд-разработчиков.Однако проблемой использования npm является высокий риск попасть в ловушку сложных зависимостей, что может сделать вас не в курсе того, какие именно библиотеки были установлены. Кроме безопасности, это может привести к таким вопросам, как "почему проект другого человека запускается, а мой нет?". Каждый проект имеет свой каталог node_modules, что может стать проблемой для пользователей Mac с ограниченным объёмом жёсткого диска.Публикации Flutter по умолчанию централизованы на платформе pub, аналогично npm они поддерживают установку через git, а файл flutter packages get
обычно сохраняется в одном месте компьютера, и несколько проектов могут использовать одну и ту же библиотеку.> - На Windows обычно находится в пути C:\Users\xxxxx\AppData\Roaming\Pub\Cache
- На macOS каталог расположен в ~/.pub-cache
Если вы не можете найти папку с плагинами, можно проверить файл .flutter-plugins
или открыть папку с плагинами следующим образом, как показано на следующем рисунке.
Наконец, стоит отметить различия в подходах к работе с плагинами между Flutter и React Native, особенно когда они содержат нативный код:
После установки плагина с нативным кодом в React Native требуется выполнение скрипта react-native link
, чтобы добавить поддержку. Например, для платформы Android это может включать изменения в файлах settings.gradle
, build.gradle
и MainApplication.java
.
В случае Flutter используется файл .flutter-plugins
, который хранит ключевые данные и пути к плагинам с нативным кодом. Затем скрипты Flutter динамически добавляют этот нативный код при помощи чтения этих данных. В конце концов создается игнорируемый файл GeneratedPluginRegistrant.java
, что позволяет разработчику не беспокоиться о процессе импорта.
Поэтому в плане работы с плагинами Flutter немного превосходит React Native.
Выходные данные после компиляции в React Native обычно представляют собой файлы bundle, такие как index.android.bundle
для Android и main.jsbundle
для iOS.
Выходные данные после компиляции в Flutter для Android могут включать:
isolate_snapshot_instr
— инструкции для разделённого изолятораisolate_snapshot_data
— данные для разделённого изолятораvm_snapshot_data
— данные для виртуальной машиныvm_snapshot_instr
— инструкции для виртуальной машины⚠️ Обратите внимание, начиная с версии 1.7.8, Flutter для Android компилируется в чистые файлы .so.
Для iOS основной выходной файл — это App.framework, содержащий четыре части: kDartVmSnapshotData
, kDartVmSnapshotInstructions
, kDartIsolateSnapshotData
и kDartIsolateSnapshotInstructions
.
Далее рассмотрим полные результаты, представленные ниже, где сравниваются размеры релизных пакетов пустого проекта и реального проекта GSY для React Native и Flutter.
Как видно, при равных условиях React Native на Android значительно больше по размеру, чем на iOS. Это связано с тем, что iOS имеет встроенный JSCore, тогда как Android требует наличия различных динамических библиотек .so. Размер этих библиотек также был уменьшен благодаря использованию NDK. Flutter и React Native в этом отношении противоположны, так как Android уже включает Skia, поэтому приложение будет значительно меньше по сравнению с iOS, где Skia не входит в состав.Эта характеристика также проявляется в релизной версии проекта GSY.
Тип | React Native | Flutter |
---|---|---|
Пустое приложение Android | ||
Пустое приложение iOS | ||
GSY Android | ||
GSY iOS |
Замечательно, что Google Play недавно выпустил объявление "8 месяцев без поддержки 64-битных приложений, они не смогут попасть в Google Play!", которое также указывает на прекращение поддержки 32-битной версии Android Studio. Поддержка формата arm64-v8a
доступна начиная с версии React Native 0.59.
Что касается Flutter, то при сборке можно указать flutter build apk --release --target-platform android-arm64
.
Когда дело доходит до производительности, это концепция, которая интересует многих людей, но стоит отметить, что говорить о производительности вне контекста использования — это неправильно, поскольку производительность связана с качеством и сложностью кода.Сначала рассмотрим теоретическую производительность. По теории производительность Flutter выше, чем у React Native. Это связано с архитектурой этих фреймворков; отсутствие OEM виджета в Flutter позволяет ему работать напрямую с процессором и графическим процессором, обеспечивая преимущество по производительности.> Важно не использовать эмуляторы для тестирования производительности, особенно эмуляторы iOS, так как Flutter использует чистый процессор на эмуляторах iOS, тогда как реальные устройства используют аппаратное ускорение графического процессора. Также следует сравнивать производительность только в режиме release.
Различия в реализации кода могут привести к снижению производительности. Например, использование метода
saveLayer
в Flutter во время рисования может существенно влиять на производительность, так как этот метод очищает кэш рисования графического процессора, что может вызывать проблемы с падением кадров при работе над приложением. В конце представлены данные тестирования проекта GSY на платформе XiaoYu прошлого года, которые были взяты из статьи «Конец слухам — Flutter против RN: кто лучший кросс-платформенный подход?».
В качестве дополнительной информации стоит отметить, что как JavaScript, так и Dart являются однопоточными приложениями, использующими концепцию корутин для реализации асинхронных эффектов. Однако в Flutter Dart поддерживает
isolate
, который представляет собой полностью асинхронный поток выполнения, позволяющий быстро осуществлять асинхронное взаимодействие через Port. Это значительно расширяет возможности Flutter с точки зрения производительности на уровне Dart.### Седьмое. Будущее развития
Ранее была опубликована статья «Почему Airbnb отказался от React Native?», которая вызвала много недоразумений среди читателей. После официального объявления Facebook о том, что они переформатируют React Native и перезапишут значительную часть его внутренней структуры, это снова успокоило общественность.
Сообщество также отметило, что начиная с версии 0.59, React Native поддерживает такие возможности, как React Hooks, и выделило некоторые компоненты платформы из React Native в сообщество. Это позволяет более удобно обновлять и поддерживать эти компоненты отдельно, а также делает границы между React Native и React всё менее заметными.
Независимость Flutter от конкретной платформы позволяет ему быстрее развиваться в области кросс-платформенного программирования. Хотя React Native также имеет поддержку для Web и PC благодаря сторонним реализациям, его развитие замедлилось за последние годы из-за сильной привязки к конкретным платформам. В то время как Flutter объявил о поддержке Web всего за несколько месяцев, он уже начал распространяться на PC и встраиваемые устройства.
Особый интерес вызывает тема Flutter for Web. При разработке Flutter Web вы можете даже не осознавать, что работаете над веб-приложением, поскольку многие логики мобильных приложений остаются неизменными.Flutter Web использует существующую мобильную логику, но на уровне движка она адаптирована с помощью Dart2Js. Однако на данный момент Flutter Web находится в стадии технического предварительного просмотра и не рекомендован для использования в продакционной среде.
От этого можно сделать вывод, что будь то Flutter или React Native, оба будут стремиться расширять свою поддержку на большее количество платформ, а также упрощать процесс разработки внутри своих экосистем.
«Facebook работает над重构React Native,将重写大量底层»
«Будущее React Native и React Hooks»
«Погружение в архитектуру следующего поколения React Native»
«Новые достижения Flutter и будущие перспективы»
Наконец, статья завершена, глубоко вздохнув.
GitHub: https://github.com/CarGuo/
Открытый проект Flutter: https://github.com/CarGuo/GSYGithubAppFlutter
Множество примеров использования Flutter: https://github.com/CarGuo/GSYFlutterDemo
Открытая книга по использованию Flutter: https://github.com/CarGuo/GSYFlutterBook
Открытый проект React Native: https://github.com/CarGuo/GSYGithubApp
«Полное руководство по разработке приложений с Flutter»«Глубокое понимание мобильной кросс-платформенной разработки»
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )