Головна / Статті / Чистий React Native, Expo чи Flutter: підбір мобільного стеку під вашу команду

Чистий React Native, Expo чи Flutter: підбір мобільного стеку під вашу команду

Практичне порівняння чистого React Native, Expo та Flutter: як вони виглядають у коді, де є їхні слабкості, та п’ятизапитальна схема для вибору між ними.

2627 слів

Раніше вибір мобільної платформи, сумісної з різними операційними системами, полягав у визначенні того компромісу, з яким можна було б працювати. Сьогодні React Native, Expo та Flutter дозволяють створювати додатки, які виглядають нативними, працюють плавно та можуть охоплювати дуже велику аудиторію, тому чиста швидкість рідко вирішує справу. Вирішальними є інші фактори: навички вашої команди, наявний код та швидкість, з якою ви потребуєте опублікувати додаток у магазинах. У цій статті Expo розглядається як окремий варіант, а не лише доповнення до React Native; описується, як кожен з варіантів працює на практиці, та наведено короткий набір запитань, які допоможуть вибрати один з них.

Що змінилося у всіх трьох платформах

Кілька структурних слабкостей, які впливали на попередні порівняння, було усунуто:

  • Нова архітектура React Native тепер є стандартною. Вона відходить від застарілого асинхронного мосту, тож JavaScript та нативний код можуть взаємодіяти пряміше. У результаті виклики до нативних модулів стають значно ефективнішими.
  • Рендерер Impeller у Flutter замінив Skia як стандартний на мобільних пристроях. Impeller заздалегідь компілює свої шейдери, що усуває проблеми з першим запуском, спричинені компіляцією шейдерів, та забезпечує більш стабільну частоту кадрів.
  • Expo став стандартним інструментом для створення додатків на React Native. Це вже не інструмент для початківців, а серйозний інструментарій, який використовують великі компанії.

Опубліковані тестування зазвичай показують, що Flutter трохи випереджає у продуктивності відображення на інтерфейсах з великою кількістю анімацій, тоді як 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 усувають старі проблеми з асинхронним мостом, тож виклики до нативного коду відбуваються майже миттєво.
  • Спільне використання коду з вебом. Завдяки React Native Web команда, яка вже має веб-додаток на React, може повторно використовувати більшу частину його логіки та навіть деякі компоненти.
  • Недоліки

    • Ви самі відповідаєте за інструменти створення нативних версій. Невідповідності версій 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. Раніше про нього говорили як про середовище з обмеженими можливостями без доступу до нативного коду. Однак це вже не так: завдяки технології prebuild, що також називається continuous native generation (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. Маршрутизація на основі файлів із функціями глибокого посилання та універсального навігування для вебу та нативних додатків доступні з самого початку.
  • Великий ретельно підібраний SDK. Такі пакети, як expo-camera, expo-notifications, expo-location та expo-image, інтегровані, документовані та сумісні за версіями, щоб вони працювали разом.
  • Плагіни для налаштувань. Ви можете змінювати файли нативного проекту, такі як Info.plist та AndroidManifest.xml, через app.json, замість того щоб редагувати їх вручну, що забезпечує відтворюваність процесів CI.
  • Відсутність прив’язки. В основі все одно залишається React Native. Виконання команди npx expo prebuild генерує нативні папки кожного разу, коли потрібен повний нативний контроль.
  • Недоліки

    • Niche SDKs потребують додаткової роботи. Деякі спеціалізовані нативні SDK все ще вимагають створення власного плагіна налаштувань або нативного модуля, що потребує трохи більше зусиль, ніж у чистому React Native, хоча різниця поступово зменшується.
    • Пастка Expo Go. Чрезмерна залежність від Expo Go може приховати той факт, що користувацький нативний модуль не буде працювати, доки ви не створите версію для розробки. Це часто стає причиною труднощів у початківців.
    • Витрати на послуги. UAS 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.
  • Чудові інструменти. DevTools, функція гарячого завантаження та послідовний, добре задокументований каталог віджетів роблять щоденний цикл розробки приємним.
  • Відсутній інтерпретатор у момент виконання. Компіляція заздалегідь у машинний код означає, що під час виконання немає двигуна JavaScript чи мосту для його обробки.
  • Перевірено у великих масштабах. Google підтримує цей фреймворк, а також великі продакшн-додатки, як-от Google Pay, додатки BMW та Xianyu від Alibaba, використовують його.
  • Недоліки

    • Екосистема 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, складна анімація чи версія для вебу — вже протягом перших тижнів, а не під час випуску.