Все уроки
Мобильные приложенияСредний16 мин чтенияВ моём стеке

React Native

Одна React-кодовая база, два по-настоящему нативных приложения — и как построить такое с нуля.

  • React Native 0.86
  • New Architecture
  • Navigation
  • Native modules
  • Reanimated
Шаг 1/5
Одна кодовая база на TypeScript: React-компоненты, состояние и бизнес-логика пишутся один раз.

За 30 секунд

React Native позволяет писать мобильное приложение на React и TypeScript, при этом экраны отрисовываются настоящими компонентами iOS и Android. Ваш JavaScript выполняется в движке Hermes и общается с нативным кодом напрямую через New Architecture. С хорошей структурой — навигацией, сторами, API-слоем и дизайн-токенами — одна команда может выпускать и поддерживать оба приложения из одной кодовой базы.

Почему я это использую

  • Я использую React Native, потому что одна кодовая база на TypeScript даёт клиенту приложения и для iOS, и для Android, которые делят бизнес-логику с его веб-фронтендом на React.
  • Я строю приложения на общем внутреннем шаблоне, поэтому авторизация, доступ к API, темы, push и принудительное обновление решены один раз, и каждое новое приложение стартует с рабочей основы.
  • New Architecture и Hermes дают достаточно хорошую производительность, чтобы пользователи не замечали разницы в приложениях для e-commerce, лояльности и бронирования.
  • Когда функции нужна платформа — платёжный SDK, сканирование камерой, карты, — я могу перейти на Kotlin или Swift для этого одного модуля, а всё остальное оставить на React.

Как это работает

01Как работает React Native

В вашем приложении два мира. Мир JavaScript выполняет React-компоненты, состояние и бизнес-логику внутри Hermes — JavaScript-движка, созданного для мобильных устройств, который компилирует код в байткод на этапе сборки, поэтому приложения запускаются быстрее и потребляют меньше памяти. Нативный мир владеет настоящими объектами UIView и Android View, камерой, keychain и так далее. React описывает, как должен выглядеть экран; React Native превращает это описание в нативные view. <View> становится UIView на iOS и ViewGroup на Android; <Text> становится нативным текстом. WebView нет: прокрутка, жесты и ввод текста нативные, поэтому RN-приложения ощущаются как настоящие приложения. Для раскладки используется Yoga — движок, реализующий Flexbox, поэтому один и тот же стиль работает на обеих платформах.

02New Architecture: JSI, Fabric, TurboModules

Старая архитектура отправляла каждое сообщение между JavaScript и нативным кодом как сериализованный JSON через асинхронный «мост» (bridge) — очередь, которая становилась узким местом для анимаций и больших списков. New Architecture, включённая по умолчанию с 0.76 и единственно возможная с 0.82, заменяет его. JSI (JavaScript Interface) позволяет JavaScript держать прямые ссылки на C++ объекты и вызывать их синхронно, без сериализации. Fabric — новый рендерер: он строит дерево UI на C++, поддерживает конкурентные возможности React 18+ и умеет синхронно измерять и обновлять раскладку. TurboModules — нативные модули, которые загружаются лениво при первом использовании и типизируются через Codegen, генерирующий C++ связку из вашей спецификации на TypeScript, так что ошибки проявляются на этапе сборки. Практический эффект: более плавные жесты и анимации (Reanimated 4 требует New Architecture), более быстрый запуск, а библиотеки должны быть совместимы с New Architecture — проверяйте до установки.

  • JSI: прямые синхронные вызовы JS ↔ C++, без JSON-моста
  • Fabric: рендерер на C++, конкурентный React, синхронная раскладка
  • TurboModules + Codegen: ленивые типобезопасные нативные модули
  • Hermes: движок с компиляцией в байткод, по умолчанию на обеих платформах

03Bare React Native или Expo?

Команда React Native теперь рекомендует начинать с фреймворка, и главный из них — Expo. Expo даёт большой набор поддерживаемых нативных модулей, файловую маршрутизацию (Expo Router), config plugins, которые сами редактируют нативные проекты, development builds и EAS для облачных сборок, публикации в сторы и OTA-обновлений. «Bare» означает, что вы напрямую владеете папками ios/ и android/ (проект создаётся через React Native Community CLI). Bare даёт полный контроль и подходит командам с нативным опытом или тяжёлыми кастомными нативными SDK. Граница размылась: Expo-приложения могут включать любой нативный код через config plugins или prebuild, а bare-приложения могут использовать модули Expo. Моё правило: для нового продукта со стандартными потребностями начинайте с Expo; для приложения, которое должно интегрировать необычные нативные SDK или следует существующему нативному шаблону, bare подходит — просто обновляйтесь регулярно.

bash
# Expo (recommended start)
npx create-expo-app@latest my-app --template default
cd my-app && npx expo start
 
# Bare React Native (Community CLI)
npx @react-native-community/cli@latest init MyApp
cd MyApp && npx react-native run-ios

04Реальное приложение с нуля: структура, окружения, API, состояние

Структурируйте код по фичам, а не по типам файлов, чтобы экран, его хуки и компоненты жили вместе. Держите три слоя раздельно. API-слой — это один HTTP-клиент с базовым URL, заголовком авторизации, обновлением токена и таймаутами; адаптеры превращают ответы бэкенда в типы приложения, поэтому переименованное поле на бэкенде меняет один файл, а не двадцать экранов. Сторы (Zustand лёгкий и хорошо работает) хранят состояние приложения: сессию, корзину, настройки; экраны читают сторы и вызывают действия, но никогда не делают запросы напрямую. Для серверных данных TanStack Query добавляет кэширование, повторы и перезапрос при возврате фокуса. Окружения: держите URL API и ключи отдельно для каждой сборки (dev, staging, prod) через конфигурацию на этапе сборки, никогда не хардкодьте их и никогда не кладите в приложение настоящие секреты — всё, что есть в бандле, можно извлечь. Автоматически повторяйте только идемпотентные запросы (GET, PUT с ключами); повторённый POST может создать дублирующийся заказ.

bash
src/
  app/          # navigation, providers, deep links
  features/
    auth/       # screens, hooks, components for sign-in
    cart/
    orders/
  shared/
    api/        # http client, adapters, endpoints
    stores/     # zustand stores (session, cart, settings)
    ui/         # Button, Text, Card — built on tokens
    theme/      # colors, spacing, typography tokens
    i18n/       # en / hy / ru dictionaries
  native/       # bridges to custom native modules

05Навигация, поток авторизации и push

React Navigation (stack, tabs, drawers поверх нативных экранов) и Expo Router (файловая маршрутизация на основе React Navigation) — стандартный выбор. Моделируйте авторизацию как состояние навигации: корневой навигатор показывает стек авторизации или основные вкладки в зависимости от наличия сессии, так что неавторизованный пользователь не сможет попасть на приватные экраны по deep link. Храните токены в Keychain/Keystore (react-native-keychain или expo-secure-store), никогда не в AsyncStorage. Позвольте гостям просматривать приложение и закрывайте только действия, которым нужен аккаунт, возвращая пользователя в то же место после входа. Настраивайте deep links и Universal/App Links с самого начала; от них зависят push-уведомления, письма и QR-коды. Для push регистрируйте токен устройства после входа и отправляйте его на бэкенд, запрашивайте разрешение в осмысленный момент, а не при первом запуске, и обрабатывайте нажатия на уведомления навигацией по тем же маршрутам deep link.

tsx
const Stack = createNativeStackNavigator<RootParams>();
 
export function RootNavigator() {
  const session = useSession((s) => s.session);
  return (
    <Stack.Navigator screenOptions={{ headerShown: false }}>
      {session ? (
        <Stack.Screen name="Main" component={MainTabs} />
      ) : (
        <Stack.Screen name="Auth" component={AuthStack} />
      )}
      <Stack.Screen name="ForceUpdate" component={ForceUpdateScreen} />
    </Stack.Navigator>
  );
}

06Стили, дизайн-токены и производительность списков

Каскада CSS нет: каждый компонент получает объект стилей, а StyleSheet.create делает их статичными. Соберите небольшую тему из токенов — цвета (светлая и тёмная тема), шкала отступов, радиусы, типографика — и несколько базовых компонентов (Text, Button, Card, Screen), которые используют только токены. Тогда в экранах никогда не будет сырых hex-значений или магических чисел, а тёмная тема или ребрендинг — это один файл. Учитывайте safe areas (react-native-safe-area-context), Dynamic Type/масштабирование шрифтов и edge-to-edge раскладку на Android. Длинные списки никогда не рендерьте через map внутри ScrollView; используйте FlatList или, лучше, FlashList, который переиспользует ячейки. Давайте элементам стабильные ключи, мемоизируйте компоненты строк, не делайте тяжёлую работу прямо в renderItem и правильно подбирайте размер изображений (expo-image или библиотека с кэшированием изображений). Анимируйте через Reanimated, который выполняет анимации в UI-потоке, поэтому они остаются плавными, даже когда JavaScript занят.

tsx
export const theme = {
  color: { bg: "#FFFFFF", text: "#111111", primary: "#0A6CFF", muted: "#6B7280" },
  space: { xs: 4, sm: 8, md: 16, lg: 24, xl: 32 },
  radius: { sm: 6, md: 12, pill: 999 },
  font: { body: 16, title: 22, caption: 13 },
} as const;
 
<FlashList
  data={products}
  keyExtractor={(p) => p.id}
  renderItem={({ item }) => <ProductRow product={item} />}
/>

07Нативные модули, различия платформ и отладка

Когда подходящей библиотеки нет — региональный платёжный SDK, принтер, кастомный сканер, — напишите TurboModule: спецификация на TypeScript, Codegen, затем реализации на Kotlin/Swift (Expo Modules API — более дружелюбная альтернатива). Держите интерфейс маленьким и основанным на promise. Ожидайте различий между платформами: процессы запроса разрешений, кнопка «назад» на Android, поведение клавиатуры, тени (shadow-свойства на iOS против elevation на Android), выбор даты, шрифты и более жёсткие ограничения фоновой работы на iOS. Используйте Platform.select или суффиксы файлов (Button.ios.tsx) только там, где поведение действительно отличается. Отладка: React Native DevTools (нажмите j в Metro) даёт отладчик в стиле Chrome, консоль, дерево React-компонентов и профилировщик для Hermes. Для нативных падений используйте логи Xcode и Android Studio (Logcat), для продакшена — Crashlytics или Sentry, а performance monitor — чтобы следить за частотой кадров JS и UI. Тестируйте на реальном слабом Android-телефоне — симулятор врёт о производительности.

Когда применять

  • Вам нужны приложения для iOS и Android с в основном общим UI и бизнес-логикой при ограниченном бюджете.
  • Ваша команда уже знает React и TypeScript или делит код и типы с веб-приложением на React.
  • Приложения для e-commerce, бронирования, лояльности, CRM и контента, где важен нативный внешний вид и ощущение, но UI стандартный.
  • Вы хотите OTA-обновления для исправлений в JavaScript и быструю итерацию с единым процессом релиза.

Когда не стоит

  • Игры с тяжёлой графикой или AR — используйте игровой движок (Unity, Godot) или нативную разработку.
  • Приложения, которые в основном состоят из глубоких функций платформы (циферблаты часов, сложные виджеты, обработка аудио в фоне) с небольшим объёмом общего UI.
  • Команда с сильными нативными iOS- и Android-инженерами и без опыта в React.

Альтернативы

  • Flutter

    Рисует собственный UI своим движком на Dart; очень единообразные пиксели, меньше нативного ощущения, отдельный язык.

  • Нативная разработка (Swift / Kotlin)

    Лучшая интеграция с платформой и производительность, но две кодовые базы и две команды.

  • Kotlin Multiplatform

    Общая бизнес-логика на Kotlin при нативном UI (или Compose Multiplatform).

  • PWA / Capacitor

    Веб-приложение в оболочке; дешевле всего для контентных приложений, но самое слабое нативное ощущение и присутствие в сторах.

Лучшие практики

  1. Начинайте с шаблона, в котором уже подключены авторизация, API-клиент, тема, i18n, push и принудительное обновление.
  2. Держите экраны тонкими: данные через API-слой с адаптерами, состояние в сторах, UI из токенов.
  3. Храните токены в Keychain/Keystore; никогда не кладите секреты в бандл.
  4. Перед добавлением каждой библиотеки проверяйте поддержку New Architecture и то, поддерживается ли она.
  5. Используйте FlashList/FlatList для списков и Reanimated для анимаций; профилируйте на слабом Android-устройстве.
  6. Обновляйте React Native регулярно (Upgrade Helper), а не перепрыгивайте через много версий сразу.
  7. Настройте Crashlytics или Sentry, deep links и переключатель принудительного обновления до первого релиза.

Частые ошибки

  • Делать запросы прямо в экранах и разносить имена полей бэкенда по всему UI, из-за чего каждое изменение API ломает множество файлов.
  • Рендерить длинные списки через ScrollView + map, вызывая скачки памяти и пропуски кадров.
  • Хранить токены в AsyncStorage или класть приватные API-ключи в env-файлы, которые попадают в бандл приложения.
  • Запрашивать все разрешения при первом запуске, из-за чего пользователи навсегда отклоняют push и геолокацию.
  • Тестировать только на симуляторе iOS и обнаруживать проблемы с клавиатурой, кнопкой «назад» и производительностью на Android уже после релиза.

Глубже

08Потоки и конвейер рендеринга

Приложение на React Native работает в нескольких потоках. JavaScript-поток выполняет React, обновления состояния и бизнес-логику в Hermes. Главный (UI) поток владеет нативными view, касаниями и циклом кадров платформы. Fabric рендерит в три фазы. Render: React создаёт элементы, а Fabric строит shadow tree из C++-узлов. Commit: Yoga рассчитывает layout для этого дерева, часто в фоновом потоке, и новое дерево становится следующим для показа. Mount: различия применяются к реальным нативным view в UI-потоке. При 60 Hz на каждый кадр есть около 16.7 ms, при 120 Hz — около 8 ms. Если JavaScript-поток занят разбором большого JSON-ответа или перерендером большого дерева, касания по-прежнему приходят, но React не может на них ответить, и приложение кажется зависшим. Поэтому worklet в Reanimated выполняют анимации в UI-потоке, а startTransition позволяет пометить несрочные обновления как прерываемые.

09Пишем TurboModule с Codegen

TurboModule начинается с типизированной JavaScript-спецификации. Имя файла должно начинаться с Native, а в package.json объявляется codegenConfig, который сообщает Codegen, где лежат спецификации. Во время сборки Codegen читает спецификацию и генерирует C++ и платформенные интерфейсы; ваши классы на Kotlin и Objective-C++ или Swift их реализуют, а компилятор ругается, если сигнатура метода не совпадает. Держите интерфейс маленьким: простые типы данных, promise для всего медленного и события для потоков обновлений, например статуса платежа. Никогда не блокируйте JavaScript-поток синхронными нативными вызовами, которые обращаются к сети или диску; синхронные методы — только для дешёвых чтений. Если в команде мало нативных разработчиков, Expo Modules API предлагает более простой DSL на Swift и Kotlin, который работает и в bare-приложениях.

ts
// specs/NativePayments.ts
import type { TurboModule } from "react-native";
import { TurboModuleRegistry } from "react-native";
 
export interface Spec extends TurboModule {
  isAvailable(): boolean; // cheap, synchronous
  startPayment(amountMinor: number, currency: string): Promise<string>;
}
 
export default TurboModuleRegistry.getEnforcing<Spec>("NativePayments");
 
// package.json
// "codegenConfig": { "name": "PaymentsSpec", "type": "modules",
//   "jsSrcsDir": "specs", "android": { "javaPackageName": "com.example.payments" } }

10Время запуска и размер бандла

Холодный старт — это первое впечатление, и большая его часть под вашим контролем. Hermes уже поставляет заранее скомпилированный байткод, поэтому оставшаяся стоимость — в основном то, что ваш код делает при загрузке модулей: импорт всех экранов и тяжёлых библиотек сразу, синхронное чтение больших данных из хранилища или инициализация множества SDK до первого кадра. Загружайте экраны лениво, откладывайте аналитику и некритичные SDK до первого рендера и держите splash screen только до готовности первых значимых данных. Измеряйте на release-сборке на Android-устройстве среднего класса и никогда — в режиме разработки, который в разы медленнее. На Android включите минификацию R8 для release и сохраняйте её mapping-файлы для Crashlytics. Следите за размером JavaScript-бандла со временем: одна библиотека для дат или графиков может добавить сотни килобайт, которые скачивает и разбирает каждый пользователь.

11Безопасность и устойчивость в офлайне

Исходите из того, что бандл прочитают: любой может распаковать APK или IPA и изучить байткод Hermes и ассеты. Секреты остаются на сервере; приложение хранит только пользовательские токены в Keychain или Keystore. Чтобы усложнить злоупотребление API, добавьте аттестацию устройства (Play Integrity на Android, App Attest на iOS) и проверяйте её на сервере. Certificate pinning блокирует часть атак с перехватом, но может отрезать всех пользователей, если сертификат неожиданно сменится, поэтому пиньте публичные ключи, держите резервный ключ и удалённый kill-switch. Поведение в офлайне нужно проектировать явно: определять наличие сети, показывать закэшированные данные с понятным баннером «офлайн», складывать действия пользователя в локальную очередь и повторять их с ключами идемпотентности, чтобы повторно отправленный заказ никогда не дублировался. Очищайте кэши с персональными данными при выходе из аккаунта и исключайте чувствительные экраны из скриншотов и переключателя приложений, если этого требует предметная область.

Реальные ситуации

01

Обновление ломается, потому что библиотека не готова к New Architecture

Проблема

При переходе со старой версии React Native на 0.82 или новее сборка падает или экран крашится в стороннем модуле, написанном под старый bridge и не обновлявшемся уже год.

Как решить

Начиная с 0.82 старую архитектуру нельзя снова включить, поэтому модуль должен работать с New Architecture. Поищите совместимый релиз или форк в React Native Directory и в issues библиотеки. Многие legacy-модули всё ещё работают через interop-слой, поэтому сначала проверьте их на реальном устройстве. Если библиотека заброшена, выберите между поддерживаемой альтернативой, временным патчем через patch-package или переписыванием той небольшой части, которой вы пользуетесь, в виде TurboModule или Expo-модуля. Делайте обновление в отдельной ветке по diff из Upgrade Helper, по одной версии за шаг, и прогоните полный чеклист релиза перед merge.

02

Список товаров дёргается на дешёвых Android-телефонах

Проблема

На флагманском iPhone каталог прокручивается плавно, но на бюджетном Android-устройстве теряет кадры, показывает пустые ячейки при прокрутке и иногда упирается в нехватку памяти.

Как решить

Профилируйте release-сборку на этом устройстве с помощью React Native DevTools и performance monitor, чтобы понять, что является узким местом — JavaScript- или UI-поток. Переведите список на FlashList, мемоизируйте строку, используйте стабильные key и передавайте только примитивные props, чтобы обновление корзины не перерендеривало каждую строку. Запрашивайте у сервера миниатюры под размер ячейки вместо полноразмерных изображений и используйте компонент изображения с кэшированием. Вынесите форматирование цен и сортировку из renderItem в селектор. Добавьте пагинацию в API, чтобы приложение никогда не держало тысячи товаров. Измерьте снова и зафиксируйте результат как бюджет для будущих изменений.

03

Приложение падает только в release-сборке под Android

Проблема

В debug всё работает, но подписанная release-сборка у тестировщиков падает при запуске. Красного экрана с ошибкой нет, а stack trace в Crashlytics показывает обфусцированные имена классов.

Как решить

Воспроизведите локально через npx react-native run-android --mode release и найдите нативное исключение в adb logcat. Типичные причины: R8 удаляет классы, которые нативный SDK загружает через reflection, отсутствует значение окружения на этапе сборки или нет нативной библиотеки для одной из архитектур CPU. Добавьте документированные keep-правила SDK в proguard-rules.pro, убедитесь, что release-сборка получает правильный конфиг окружения, и проверьте, что mapping-файлы загружаются в Crashlytics, чтобы stack trace стали читаемыми. Затем добавьте в CI автоматический smoke-тест release-варианта, чтобы такой класс багов ловился до того, как его увидят тестировщики.

Шпаргалка

Запустить Android release-сборку локально
npx react-native run-android --mode release
Сбросить кэш Metro
npx react-native start --reset-cache (Expo: npx expo start -c)
Открыть React Native DevTools
нажмите j в терминале Metro
Проверить локальный тулчейн
npx react-native doctor
Выровнять версии пакетов Expo
npx expo install --check
Пересоздать нативные папки (Expo)
npx expo prebuild --clean
Установить iOS pods
cd ios && bundle exec pod install
Очистить результаты сборки Android
cd android && ./gradlew clean
Показывать только ошибки Android
adb logcat *:E
Спланировать diff обновления
react-native-community.github.io/upgrade-helper

В моих реальных проектах

  • Я построил 5 приложений на React Native (заказ еды в ресторанах, зоомагазин, спортзал, кофейная программа лояльности, инвестиции) на общем внутреннем шаблоне, пройдя путь от RN 0.81 до 0.86.
  • Самое новое приложение для заказа кофе работает на RN 0.86 с New Architecture и Hermes, Reanimated 4.5 и Zustand; слой адаптеров нормализует ответы бэкенда, поэтому переименование никогда не затрагивает экран.
  • Приложение для ресторанов, которое я веду, включает собственный нативный Android-модуль для регионального платёжного провайдера, а также принудительное обновление через Remote Config, переключаемый базовый URL API и политику повторов, которая повторяет только идемпотентные запросы.
  • Приложение для участников сети спортзалов использует VisionCamera для отметки по QR и запланированные push-кампании, с задокументированным процессом релиза.

Вопросы и ответы

Q1Чем React Native отличается от гибридного приложения на WebView?

Гибридное приложение вроде Cordova или Capacitor рендерит HTML и CSS внутри WebView, то есть весь UI — это веб-страница. React Native рендерит настоящие нативные компоненты: View становится UIView или Android ViewGroup, TextInput — это текстовое поле платформы, прокрутка и жесты нативные. JavaScript выполняется в Hermes и описывает UI, а нативная сторона его рисует. Поэтому RN-приложения ощущаются нативными и интегрируются с accessibility, клавиатурами и системным поведением. Обратная сторона — нельзя напрямую переиспользовать веб-библиотеки для DOM, а некоторым функциям нужны нативные модули.

Q2Объясните New Architecture простыми словами.

Раньше JavaScript и нативный код общались через асинхронный мост, который сериализовал каждое сообщение в JSON, — это было медленно для анимаций и больших списков. New Architecture убирает этот мост. JSI позволяет JavaScript вызывать C++ объекты напрямую и синхронно. Fabric — новый рендерер на C++, который поддерживает конкурентный React и синхронное измерение раскладки. TurboModules загружают нативные модули лениво, а Codegen генерирует типобезопасную связку из спецификаций на TypeScript. Она включена по умолчанию с 0.76 и стала единственной архитектурой с 0.82, поэтому на практике главная задача — выбирать библиотеки, которые её поддерживают.

Q3Как вы структурируете React Native проект для долгоживущего продукта?

По фичам, с понятными общими слоями. В папке каждой фичи лежат её экраны, хуки и компоненты. Общий API-слой владеет HTTP-клиентом, заголовками авторизации, обновлением токена, таймаутами и адаптерами, которые превращают ответы бэкенда в типы приложения. Сторы хранят сессию, корзину и настройки; экраны только читают сторы и вызывают действия. UI строится из нескольких базовых компонентов, использующих дизайн-токены, поэтому тёмная тема и ребрендинг обходятся дёшево. Навигация, deep links и обработка push живут в одной папке app. Так изменения остаются локальными, и несколько разработчиков или AI-агентов могут работать, не мешая друг другу.

Q4Как улучшить производительность медленного списка?

Сначала измерьте профилировщиком React Native DevTools на реальном Android-телефоне среднего уровня. Затем перейдите на FlashList или настроенный FlatList, никогда не используйте ScrollView с map. Дайте элементам стабильные ключи, мемоизируйте компонент строки и держите renderItem дешёвым — никакой сортировки или форматирования всего набора данных прямо в нём. Передавайте строке только нужные ей props, чтобы изменения несвязанного состояния не перерисовывали каждую строку. Уменьшайте изображения на сервере и используйте компонент изображения с кэшированием. Делайте пагинацию через API и переносите тяжёлые анимации в Reanimated, чтобы они выполнялись в UI-потоке.

Q5Expo или bare React Native — что вы рекомендуете?

Для большинства новых приложений я рекомендую Expo, и команда React Native тоже. Вы получаете поддерживаемые модули, development builds, config plugins для нативных изменений, EAS Build и Submit и OTA-обновления, что снимает много боли с настройкой и обновлениями. Expo не отрезает вас от нативного кода: можно добавлять собственные модули и config plugins. Bare имеет смысл, когда у вас уже есть шаблон с большим количеством нативного кода, нужны необычные SDK, которые больно оборачивать в плагины, или команда хочет напрямую управлять проектами Xcode и Gradle. В любом случае держите зависимости актуальными.

Q6Как безопасно реализовать аутентификацию в мобильном приложении?

Используйте короткоживущие access-токены вместе с refresh-токеном, которые бэкенд выдаёт после входа по паролю, OTP или через соцсети. Храните токены в iOS Keychain или Android Keystore, а не в AsyncStorage. API-клиент прикрепляет access-токен, один раз обновляет его при 401, ставит параллельные запросы в очередь на время обновления и аккуратно разлогинивает, если обновление не удалось. Корневой навигатор переключается между стеками авторизации и основным в зависимости от состояния сессии. Никогда не встраивайте в приложение приватные ключи или секреты, ограничивайте частоту OTP на сервере и поддерживайте удаление аккаунта внутри приложения, чего требует Apple.

Вопросы уровня senior

S1Как бы вы переиспользовали код между приложением на React Native и веб-приложением на React?

Сначала переиспользуйте то, в чём нет пикселей. В монорепозитории я выношу типы API, HTTP-клиент, адаптеры, схемы валидации, доменную логику и сторы в пакеты, которые импортируют оба приложения; это чистый TypeScript, и он легко тестируется. UI — отдельное решение. React Native Web может рендерить те же компоненты в вебе, и это окупается для внутренних инструментов или простых экранов, но маркетинговым сайтам и SEO-страницам обычно нужны нативные веб-компоненты и серверный рендеринг. Хорошая золотая середина — design tokens: один источник токенов питает и CSS-переменные, и нативный объект темы. Компромиссы — сложность инструментов (Metro, Next.js и TypeScript project references должны одинаково резолвить пакеты) и связность: изменение в общем пакете теперь требует тестирования обоих приложений, поэтому у общего слоя должны быть свои тесты и понятное версионирование.

S2Спроектируйте offline-first процесс оформления заказа для мобильного приложения.

Разделите чтения и записи. Чтения идут из локального кэша — TanStack Query с сохранением в хранилище или SQLite для больших объёмов — показываются сразу с отметкой времени и обновляются, когда сеть возвращается. Записи превращаются в команды в локальном outbox: каждый заказ получает сгенерированный на клиенте ключ идемпотентности, сохраняется локально, отображается как ожидающий и отправляется, когда появляется связь. Сервер должен считать ключ уникальным, чтобы повтор после таймаута возвращал исходный заказ, а не создавал второй. Конфликтам нужны бизнес-правила, а не техническая магия: если товар закончился или цена изменилась, сервер отклоняет запрос с понятной причиной, а приложение спрашивает пользователя. Компромиссы: больше сложности на клиенте, миграции локальной схемы и аккуратный UX, чтобы пользователи понимали, что подтверждено, а что ещё ожидает.

S3Как поддерживать в актуальном состоянии несколько приложений на общем шаблоне?

Относитесь к шаблону как к продукту с собственным версионированием. Общий код — API-клиент, авторизация, система тем, push, принудительное обновление — переезжает в пакеты или в явно помеченную папку core, которую приложения напрямую не редактируют; код конкретного приложения живёт отдельно. Обновления React Native сначала делаются в шаблоне, по одной minor-версии за раз, с помощью Upgrade Helper и чеклиста, который проверяет совместимость каждой зависимости с New Architecture. Затем каждое приложение забирает изменение в ветке, прогоняет release-сборку и smoke-тесты и выпускается через TestFlight и внутренний трек. Компромисс — дисциплина против свободы: чем сильнее приложение отходит от шаблона, тем дороже каждое обновление, поэтому я подталкиваю команды возвращать исправления в core, а не патчить локально.

S4Когда для нового проекта вы бы выбрали нативную разработку вместо React Native?

Когда ценность продукта живёт в самой платформе. Примеры: тяжёлая графика в реальном времени, AR, сложная фоновая работа с аудио или Bluetooth, глубокая интеграция с виджетами и часами или приложения, где большинство экранов — кастомные нативные решения с небольшим объёмом общей логики. А также когда в организации уже есть сильные отдельные команды iOS и Android: перевод их на React добавляет затрат без выгоды. React Native выигрывает, когда большая часть приложения — формы, списки, коммерция и контент, когда команда знает React и когда iOS и Android должны получать одни и те же функции одновременно. Есть и гибридный путь: нативное приложение, которое встраивает React Native для части экранов, или приложение на React Native с несколькими нативными экранами. Честный ответ — модель затрат: навыки команды, требования к паритету функций, риски производительности и долгосрочная поддержка.

Глоссарий

Hermes
JavaScript-движок, который React Native использует по умолчанию; оптимизирован для мобильных устройств, байткод компилируется во время сборки.
JSI
JavaScript Interface: C++ API, позволяющий JavaScript вызывать нативные объекты напрямую и синхронно, без JSON-bridge.
Fabric
Рендерер New Architecture, который строит дерево UI на C++ и поддерживает конкурентный React.
TurboModule
Нативный модуль New Architecture, загружаемый лениво и типизированный через спецификацию Codegen.
Codegen
Инструмент времени сборки, превращающий типизированные JavaScript-спецификации в C++ и платформенные интерфейсы для нативного кода.
Shadow tree
C++-копия дерева UI в Fabric, по которой рассчитывается layout до того, как изменения попадут в реальные нативные view.
Yoga
Кроссплатформенный layout-движок, реализующий Flexbox для компонентов React Native.
Metro
JavaScript-бандлер и dev-сервер, который отдаёт ваш код приложению и обеспечивает Fast Refresh.
Worklet
Небольшая JavaScript-функция, которую Reanimated выполняет в UI-потоке, чтобы анимации оставались плавными, пока JavaScript занят.
Config plugin
Функция Expo, которая редактирует файлы нативного проекта во время prebuild, чтобы нативная настройка оставалась воспроизводимой.

Есть идея продукта?Давайте создадим его.

Расскажите о своей идее, продукте или команде. Обычно отвечаю в течение одного рабочего дня.