React Native
Одна 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 подходит — просто обновляйтесь регулярно.
# 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 может создать дублирующийся заказ.
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.
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 занят.
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
Веб-приложение в оболочке; дешевле всего для контентных приложений, но самое слабое нативное ощущение и присутствие в сторах.
Лучшие практики
- Начинайте с шаблона, в котором уже подключены авторизация, API-клиент, тема, i18n, push и принудительное обновление.
- Держите экраны тонкими: данные через API-слой с адаптерами, состояние в сторах, UI из токенов.
- Храните токены в Keychain/Keystore; никогда не кладите секреты в бандл.
- Перед добавлением каждой библиотеки проверяйте поддержку New Architecture и то, поддерживается ли она.
- Используйте FlashList/FlatList для списков и Reanimated для анимаций; профилируйте на слабом Android-устройстве.
- Обновляйте React Native регулярно (Upgrade Helper), а не перепрыгивайте через много версий сразу.
- Настройте 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-приложениях.
// 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. Поведение в офлайне нужно проектировать явно: определять наличие сети, показывать закэшированные данные с понятным баннером «офлайн», складывать действия пользователя в локальную очередь и повторять их с ключами идемпотентности, чтобы повторно отправленный заказ никогда не дублировался. Очищайте кэши с персональными данными при выходе из аккаунта и исключайте чувствительные экраны из скриншотов и переключателя приложений, если этого требует предметная область.
Реальные ситуации
Обновление ломается, потому что библиотека не готова к 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.
Список товаров дёргается на дешёвых Android-телефонах
Проблема
На флагманском iPhone каталог прокручивается плавно, но на бюджетном Android-устройстве теряет кадры, показывает пустые ячейки при прокрутке и иногда упирается в нехватку памяти.
Как решить
Профилируйте release-сборку на этом устройстве с помощью React Native DevTools и performance monitor, чтобы понять, что является узким местом — JavaScript- или UI-поток. Переведите список на FlashList, мемоизируйте строку, используйте стабильные key и передавайте только примитивные props, чтобы обновление корзины не перерендеривало каждую строку. Запрашивайте у сервера миниатюры под размер ячейки вместо полноразмерных изображений и используйте компонент изображения с кэшированием. Вынесите форматирование цен и сортировку из renderItem в селектор. Добавьте пагинацию в API, чтобы приложение никогда не держало тысячи товаров. Измерьте снова и зафиксируйте результат как бюджет для будущих изменений.
Приложение падает только в 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, чтобы нативная настройка оставалась воспроизводимой.
Что изучить дальше
Есть идея продукта?Давайте создадим его.
Расскажите о своей идее, продукте или команде. Обычно отвечаю в течение одного рабочего дня.