Чистый React Native, Expo или Flutter: подбор мобильной платформы под вашу команду
Практическое сравнение чистого React Native, Expo и Flutter: как они выглядят в коде, в чем заключаются их слабые стороны, а также пятивопросный инструмент для выбора между ними.
Раньше выбор мобильной платформы, совместимой с несколькими операционными системами, сводился к вопросу о том, с какими компромиссами можно смириться. Сегодня React Native, Expo и Flutter позволяют создавать приложения, которые выглядят как нативные, работают плавно и могут обслуживать очень большую аудиторию, поэтому скорость редко становится решающим фактором. Решающими являются соответствие требованиям: навыки вашей команды, уже имеющийся код и скорость, с которой вам нужно выйти в магазины приложений. В этой статье Expo рассматривается как самостоятельный вариант, а не просто дополнение к React Native; описывается, как каждый из вариантов работает на практике, и в конце приводится набор коротких вопросов, которые помогут сделать выбор.
Что изменилось во всех трех платформах
Несколько структурных слабостей, которые определяли результаты более ранних сравнений, были устранены:
- Новая архитектура React Native теперь является стандартной. Она отходит от устаревшего асинхронного моста, что позволяет JavaScript и нативному коду взаимодействовать более прямо. В результате вызовы нативных модулей становятся значительно более эффективными.
- Рендерер Impeller в Flutter заменил Skia в качестве стандартного рендерера на мобильных устройствах. Impeller заранее компилирует свои шейдеры, что устраняет известные задержки, возникающие при компиляции шейдеров в первый раз, и обеспечивает более стабильную частоту кадров.
- Expo стал стандартным инструментом для создания приложений на React Native. Это уже не среда для начинающих, а производственная инфраструктура, используемая крупными компаниями.
Публикуемые тесты обычно показывают, что Flutter немного превосходит React Native по скорости отрисовки в интерфейсах с большим количеством анимаций, в то время как React Native лидирует по времени запуска, объему памяти и работе с нативными вводом-выводом. Следует относиться к конкретным цифрам с осторожностью, поскольку они сильно варьируются в зависимости от приложения и устройства. Для большинства продуктов эта разница уже не играет решающей роли. Если вы хотите узнать мнение команд, которые уже давно работают с этими выборами, наша статья «Решения между Flutter и React Native, которые проявляются только в реальных условиях» рассматривает долгосрочные аспекты.
Чистый React Native: максимальный контроль, максимальная ответственность
React Native позволяет создавать интерфейс на JavaScript или TypeScript с использованием React, в то время как фреймворк отображает нативные компоненты платформы на iOS и Android. Здесь нет WebView и не требуется пользовательский холст: элемент View превращается в нативный виджет, а элемент Text — в нативный компонент текста.
Счетчик ниже демонстрирует базовую структуру приложения в рамках Новой архитектуры с использованием рендерера Fabric. Состояние хранится в хуке useState, кнопка реализована с помощью компонента Pressable, а стили задаются один раз с использованием StyleSheet.create, что позволяет их проверять и повторно использовать.
// App.tsx — React Native (New Architecture, Fabric)
import React, { useState } from 'react';
import { View, Text, Pressable, StyleSheet } from 'react-native';
export default function App() {
const [count, setCount] = useState(0);
return (
<View style={styles.container}>
<Text style={styles.counter}>{count}</Text>
<Pressable style={styles.button} onPress={() => setCount(c => c + 1)}>
<Text style={styles.buttonText}>Tap me</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, alignItems: 'center', justifyContent: 'center' },
counter: { fontSize: 48, fontWeight: '700', marginBottom: 24 },
button: { backgroundColor: '#2563eb', paddingHorizontal: 24, paddingVertical: 12, borderRadius: 12 },
buttonText: { color: 'white', fontSize: 16, fontWeight: '600' },
});
В этом фрагменте нет ничего, что было бы специфично для Новой архитектуры; тот же код компонентов работает в обоих случаях. Изменения в архитектуре происходят на более глубоком уровне, в способе взаимодействия рендерера и нативных модулей с JavaScript.
Преимущества
- Настоящие нативные виджеты. Вы получаете собственные элементы управления платформы, поведение в области доступности и отображение текста в их оригинальном виде, а не его приближенную версию.
- Самый большой пул квалифицированных специалистов. Навыки работы с JavaScript, TypeScript и React гораздо более распространены, чем навыки, необходимые для других вариантов.
- Полный нативный доступ. У вас есть полный контроль над каталогами
ios/иandroid/, что позволяет использовать Swift, Kotlin, Objective-C или Java по мере необходимости. - npm предлагает больше пакетов от сторонних разработчиков, чем pub.dev, и большинство мобильных SDK для платежей, карт и аналитики поставляются с официальными обертками React Native.
- Более быстрые нативные вызовы. Fabric, TurboModules и JSI устраняют проблему асинхронного моста, поэтому вызовы в нативный код выполняются практически мгновенно.
Недостатки
- Инструменты сборки для нативных платформ находятся у вас. Несоответствия версий Xcode, Gradle и CocoaPods, а также конфликты зависимостей остаются частой причиной проблем.
- Обеспечение единообразия между платформами требует усилий. Поскольку фреймворк намеренно использует нативные элементы интерфейса, iOS и Android будут выглядеть по-разному, если только вы не специально не будете работать над их единообразием.
- Инфраструктура доставки — ваша ответственность. Настройка CI/CD, подпись кода и обновления через сеть — это задачи, сложные для реализации с нуля, и именно этот пробел заполняет Expo.
- Разное качество сторонних модулей. Качество обслуживания нативных модулей варьируется от отличного до совершенно заброшенного.
Практические советы
- Избегайте создания нового приложения с нуля в чистом React Native, если у вас нет конкретной причины, такой как особый нативный SDK или существующее нативное приложение, которое вы мигрируете по частям. Начните с Expo и создавайте нативные проекты только тогда, когда это необходимо.
- Используйте
react-native-reanimatedиreact-native-gesture-handlerдля интеракций, критичных с точки зрения производительности. Они обрабатывают анимации и жесты на потоке UI, поэтому загруженный JavaScript-поток не приводит к потере кадров. - Оставьте Hermes включенным — это стандартный движок. Он заранее компилирует JavaScript в байт-код, что значительно сокращает время запуска и использование памяти по сравнению с JavaScriptCore.
Expo: React Native с встроенной инфраструктурой
Expo — это фреймворк и набор сервисов, построенных на основе React Native. Ранее о нем ходили слухи, что это ограниченная среда без возможности доступа к нативному коду. Однако это уже не так: благодаря технологии предварительной компиляции, также называемой непрерывной генерацией нативного кода (CNG), Expo работает с всей экосистемой нативных модулей.
На приведённом ниже экране используется Expo Router, который сопоставляет файлы в каталоге app/ с маршрутами таким же образом, как Next.js для веба. app/index.tsx является главным маршрутом, а router.push('/profile') перенаправляет на файл, определяющий маршрут /profile. Глубокие ссылки и веб-адреса формируются по той же структуре.
// app/index.tsx — Expo Router (file-based routing)
import { View, Text, Pressable } from 'react-native';
import { useRouter } from 'expo-router';
export default function HomeScreen() {
const router = useRouter();
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', gap: 16 }}>
<Text style={{ fontSize: 24, fontWeight: '600' }}>Welcome</Text>
<Pressable onPress={() => router.push('/profile')}>
<Text style={{ color: '#2563eb' }}>Go to Profile</Text>
</Pressable>
</View>
);
}
Для создания и запуска проекта требуется три команды. Первая создаёт структуру приложения, а npx expo start запускает сервер разработки, позволяя открыть приложение на устройстве или симуляторе.
# Spin up a new project in under a minute
npx create-expo-app@latest my-app
cd my-app
npx expo start
Преимущества
- Отсутствие встроенных инструментов для начала работы. Для большей части разработки не требуется Xcode или Android Studio; тестирование можно проводить на физическом устройстве с помощью Expo Go или специального клиента для разработки.
- EAS Build. Сборка приложений для iOS и Android выполняется в облаке с любой операционной системы, поэтому сборку для iOS можно создать на Windows или Linux.
- EAS Update. Обновления через сеть мгновенно доставляют изменения в JavaScript и ресурсы пользователям, без необходимости ожидания проверки в магазине, при условии отсутствия изменений в нативном коде.
- Expo Router. Маршрутизация на основе файлов с глубокой привязкой и универсальным навигационным функционалом для веба и нативных приложений доступна сразу после установки.
expo-camera, expo-notifications, expo-location и expo-image интегрированы, документированы и согласованы по версиям, чтобы они работали вместе.Info.plist и AndroidManifest.xml, из файла app.json, вместо того чтобы редактировать их вручную, что обеспечивает воспроизводимость процессов сборки в CI.npx expo prebuild генерирует нативные папки каждый раз, когда требуется полный контроль над нативным кодом.Недостатки
- Специализированным SDK требуется дополнительная работа. Некоторые специализированные нативные SDK всё ещё требуют создания собственного плагина настройки или нативного модуля, что требует немного больше усилий, чем при использовании чистого React Native, хотя разница постепенно сокращается.
- Ловушка Expo Go. Чрезмерная зависимость от Expo Go может скрыть тот факт, что пользовательский нативный модуль не будет работать до момента создания разработочной версии. Это часто становится причиной трудностей у новичков.
- Расходы на обслуживание. UAT Build и Update имеют бесплатные тарифы, но серьёзные команды, работающие в производственных условиях, обычно переходят на платные тарифы; такие расходы избегаются при использовании чистого React Native с самостоятельно развернутой системой CI.
- Наследуемые ограничения. Будучи слоем поверх React Native, Expo сохраняет свои недостатки: различия в интерфейсах платформ и поток JavaScript, который может стать узким местом при интенсивных вычислениях.
Практические советы
- Выполните команду
npx expo prebuild, когда вам нужна нативная библиотека, которую Expo не поддерживает. Она создает папкиios/иandroid/по мере необходимости, так что нативный код всегда находится в доступе. В нашей статье о том, как использовать Expo prebuild и CNG для обработки нативных папок в качестве результата сборки подробно описан процесс работы. - Используйте EAS Update для выпуска быстрых исправлений, но не для добавления функций, меняющих поведение приложения на уровне нативного кода. Правила App Store ограничивают то, что может изменяться в загружаемом коде, а развертывание новых функций под видом OTA-обновления может привести к отклонению приложения; прочитайте текущие правила Apple и Google перед тем, как на это полагаться.
- Внедряйте Expo Router с самого начала нового проекта. Переоборудование существующей системы навигации под файловую систему маршрутизации сопряжено с большими трудностями.
expo doctor перед каждой сборкой для выпуска. Он выявляет несоответствия зависимостей и версий, которые в противном случае проявлялись бы как необъяснимые сбои при сборке нативного кода.Flutter: контроль над каждым пикселем
Flutter использует совершенно иной подход. Вместо преобразования в компоненты платформы он сам рисует всё интерфейсное пространство с помощью своего движка Impeller, а код на Dart заранее компилируется в нативный машинный код для процессоров ARM или x86.
Показатель на Dart ниже соответствует примеру React Native. MyApp оборачивает приложение в MaterialApp, CounterScreen является объектом типа StatefulWidget, класс состояния которого хранит переменную _count; нажатие кнопки вызывает метод setState, который сообщает Flutter о необходимости пересоздания этой части интерфейса с новым значением.
// main.dart
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: CounterScreen());
}
}
class CounterScreen extends StatefulWidget {
const CounterScreen({super.key});
@override
State<CounterScreen> createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('$_count', style: const TextStyle(fontSize: 48, fontWeight: FontWeight.bold)),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () => setState(() => _count++),
child: const Text('Tap me'),
),
],
),
),
);
}
}
Рабочий процесс в командной строке охватывает весь жизненный цикл: создание проекта, его запуск с функцией горячего перезагрузки во время разработки, а также формирование файлов для публикации в Google Play (пакет приложения) и App Store (файл IPA).
flutter create my_app
cd my_app
flutter run # hot reload in under a second
flutter build appbundle --release # Android
flutter build ipa --release # iOS
Преимущества
- Идентичный интерфейс повсюду. Благодаря тому, что Flutter сам отрисовывает каждый виджет, его поведение и внешний вид совпадают на iOS, Android, вебе и на ПК без особенностей конкретной платформы.
- Высокая производительность анимаций. Благодаря заранее скомпилированным шейдерам и прямому доступу к GPU через Impeller, Flutter обычно опережает другие решения по частоте кадров в сложных интерфейсах с интенсивными анимациями.
- Шесть целевых платформ из одного кодового базиса. iOS, Android, веб, Windows, macOS и Linux — все они могут быть собраны из одного и того же кода на языке Dart.
Недостатки
- Экосистема Dart меньше. Найти специалистов сложнее, чем для JavaScript или TypeScript, и даже опытным разработчикам обычно требуется несколько недель, чтобы стать продуктивными.
- Возможно ощущение некоторой «чужеродности». Поскольку язык не использует встроенные компоненты платформы, внимательные пользователи могут заметить небольшие различия в поведении, хотя это уже сильно уменьшилось.
- Более крупные бинарние файлы. Движок Flutter входит в состав каждого приложения, поэтому размер пакетов обычно больше, чем у аналогичных приложений на React Native.
- Меньше специализированных пакетов. pub.dev хорош, но имеет меньший объем ресурсов, чем npm, особенно в отношении оберток вокруг специализированных нативных SDK.
- Отсутствие возможности повторного использования кода из веб-версии React. Компании, уже имеющие развитую веб-базу кода на React, не могут использовать её в своих веб-приложениях.
Практические советы
- С самого начала включите
flutter analyzeс строгими правилами проверки кода. Защита от значений null в Dart действительно является преимуществом, но только если не нарушать её использованием типовdynamicповсюду. - Для проектов, выходящих за рамки прототипа, выберите подход к управлению состоянием, такой как Riverpod или Bloc; одного лишь
setStateнедостаточно для создания полноценного приложения. - Прежде чем считать, что у вас проблема с задержками отображения, посмотрите информацию в разделе Performance инструментов DevTools. Инструмент Impeller уже устранил большинство проблем с медленной компиляцией шейдеров в прошлом.
- Если веб-приложения имеют большое значение, заранее протестируйте Flutter Web. Его механизмы отрисовки отличаются от тех, что используются в мобильных приложениях, а размер пакета может удивить. На момент написания статьи Flutter активно развивает свои рендереры на основе CanvasKit, поэтому проверьте актуальную документацию, какие опции всё ещё поддерживаются.
Пять вопросов, которые помогут сделать выбор
Рассмотрите их в порядке. Обычно первый вопрос с четким ответом уже определяет исход.
- Работаете ли ваши сотрудники уже с React и JavaScript? Если да, оставайтесь в экосистеме React Native и используйте Expo по умолчанию. Если нет и у вас есть свобода выбора, Flutter и Expo оба являются хорошими вариантами; выберите язык, который ваша команда предпочла бы изучить.
- Вам нужен дизайн, идентичный по пикселям, или сложные пользовательские анимации, как в играх, творческих инструментах или приложениях с интенсивной визуализацией? Flutter является более подходящим выбором по умолчанию.
- Хотите ли вы делиться кодом или компонентами с существующим веб-приложением на React? React Native, благодаря React Native Web, обладает реальным преимуществом; Flutter требует полной переработки для работы в вебе.
- Нужно ли вам внедрять исправления, написанные только на JavaScript, без проверки магазина, или создавать приложения для iOS без Mac? EAS Update и EAS Build от Expo напрямую решают оба этих вопроса.
- Встраиваете ли вы кроссплатформенные экраны в большое существующее нативное приложение? Чистый React Native или функция добавления компонентов в приложение от Flutter лучше подходят для этой цели, чем совершенно новый проект Expo.
Рекомендации
- Для команды, работающей с React или JavaScript и создающей новое приложение, выберите Expo. Он устраняет большинство проблем, связанных с React Native в плане инструментов для работы с нативным кодом, процессов CI/CD и обновлений через OTA, при этом сохраняя полную мощь нативного кода там, где она необходима.
- Выберите Flutter, когда важнее качество интерфейса, производительность анимаций и возможность использования приложения на мобильных устройствах, ПК и вебе, чем возможность повторного использования экосистемы JavaScript, или когда у команды нет явных предпочтений в языках и она начинает работу с нуля.
- Используйте чистый React Native в ситуациях, когда требуется прямой контроль над проектами на нативном уровне, например, при наличии большой существующей базы нативного кода или необычных требованиях к его интеграции.
Заключение
Эти три фреймворка достаточно сблизились по производительности и уровню зрелости, поэтому возможности самого фреймворка редко становятся узким местом. Определяющими факторами являются сотрудники вашей команды, уже имеющийся у вас код и скорость выпуска продукта. Рассматривайте Expo как стандартный способ использования React Native, оставляйте чистый React Native для случаев, когда ключевым является управление нативными проектами, а Flutter — когда контроль над отображением и возможность работы на нескольких платформах имеют большее значение, чем преимущества использования JavaScript. Какой бы вариант вы ни выбрали, проверьте самую рискованную часть вашего приложения — будь то нативный SDK, сложная анимация или версия для веба — уже в первые недели, а не в момент выпуска.