React Native в продакшене: 14 месяцев разработки кросс-платформенного приложения
Мы запустили приложение для сервисного бизнеса в App Store и Google Play в начале 2016 года. В ноябре 2017-го подводим итоги 14 месяцев поддержки и развития. Реальное время - чат, карты, оплата. Команда - два разработчика с опытом веб-разработки на React.
Честный отчёт: что обещали, что получили.
Обещание vs реальность
Обещание: «Пиши один раз, запускай везде». Один код для iOS и Android, веб-разработчики могут делать мобильные приложения, быстрый выход на рынок.
Реальность: 75–80% общего кода, 20–25% платформо-специфичного. Веб-разработчики могут делать мобильные приложения, но мобильная разработка - это отдельная дисциплина, и знать её основы всё равно придётся. Быстрый выход на рынок - правда, особенно по бизнес-логике.
Что работало отлично
Бизнес-логика
Redux-стор, API-запросы, трансформации данных, правила бронирования - 100% общего кода. Исправление бага в логике расчёта стоимости заказа деплоится одновременно на iOS и Android.
Навигация
К концу 2016 года React Navigation достаточно созрел для продакшена. Стек навигации, табы, модальные экраны - на JavaScript. Платформенные переходы (свайп назад на iOS, кнопка «назад» на Android) обрабатываются автоматически.
Формы и ввод данных
Стандартные текстовые поля, выпадающие списки, выбор даты - в основном нормально. Платформенный стайлинг нужен, логика - общая.
JavaScript-мост: где производительность подвела
Архитектура React Native 2017 года: JavaScript-поток ↔ Мост ↔ Нативный поток. Каждое взаимодействие через мост добавляет задержку.
Это проявилось в двух местах:
Анимации, управляемые жестами
// Это выглядело нормально в разработке,
// но тормозило на бюджетных Android-устройствах в продакшене.
// JS вычисляет позицию → Мост → Нативный рендер
class SwipeableCard extends Component {
handleGesture(event) {
this.setState({
translateX: event.nativeEvent.translationX
});
// setState → JS ре-рендер → Мост → Нативный
// 60 FPS = 16ms на кадр. Мост добавляет 4–8ms. Математика не сходится.
}
}
Решение - useNativeDriver: true. Анимация выполняется полностью в нативном потоке, мост не задействован:
const translateX = new Animated.Value(0);
// Выполняется в нативном потоке - без мосте на каждый кадр
const onGestureEvent = Animated.event(
[{ nativeEvent: { translationX: translateX } }],
{ useNativeDriver: true }
);
Длинные списки
ScrollView рендерит все дочерние элементы сразу. При 200+ элементах - разрушение производительности. FlatList виртуализирует: рендерит только видимые элементы плюс небольшой буфер.
// Плохо: рендерит все 300 заказов одновременно
<ScrollView>
{orders.map(order => <OrderCard key={order.id} {...order} />)}
</ScrollView>
// Хорошо: рендерит ~15-20 элементов независимо от размера списка
<FlatList
data={orders}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => <OrderCard {...item} />}
getItemLayout={(data, index) => ({
length: ORDER_HEIGHT,
offset: ORDER_HEIGHT * index,
index,
})}
removeClippedSubviews={true}
/>
Когда пришлось писать нативный код
Три модуля потребовали нативной разработки:
1. Фоновая геолокация (iOS). Стандартный API геолокации RN в 2017 году не поддерживал правильный фоновый режим для iOS - нам нужно было отслеживать курьеров. Написали модуль на Objective-C.
2. Камера с оверлеем. Стандартный компонент Camera не поддерживал нужный UI поверх камеры. Написали нативный модуль на Swift и Kotlin и сбриджировали его.
3. Биометрическая аутентификация. Готовые библиотеки для TouchID давали нестабильные результаты. Написали нативный модуль.
Нативный bridge API документирован хорошо, но требует знания iOS и Android разработки. Обещание «без мобильных знаний» здесь ломается.
Платформенные различия на практике
import { Platform, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
header: {
// iOS: статус-бар накладывается на контент
paddingTop: Platform.OS === 'ios' ? 20 : 0,
},
card: {
// iOS и Android имеют разные модели теней
...Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.15,
shadowRadius: 4,
},
android: {
elevation: 4, // Android использует elevation
},
}),
},
});
Рендеринг текста, поведение клавиатуры, диалоги разрешений, кнопка «назад» - всё требовало платформо-специфической обработки в разных местах кодовой базы.
Итог через 14 месяцев
React Native подходит для:
- Приложений с тяжёлой бизнес-логикой (общая для платформ)
- MVP стартапов, где время выхода на рынок критично
- Команд с опытом React
- Приложений со стандартным UI (списки, формы, навигация)
React Native не подходит для:
- Приложений с жесте-управляемыми интерфейсами
- Игр и графически интенсивных приложений
- Глубокой интеграции с железом
Мы выпустили продакшен-приложение для 12 000 пользователей на iOS и Android командой из двух React-разработчиков. С нативной разработкой это было бы нереально. Трейдоффы были реальными, но и скорость разработки тоже.
В 2024 году архитектура React Native переписана (JSI вместо старого моста). Flutter появился как сильная альтернатива. Кросс-платформенная разработка стала лучше - но базовые трейдоффы остались прежними.