React Native — мобильные приложения на JavaScript
React Native позволяет писать настоящие мобильные приложения на JavaScript и TypeScript, используя нативные компоненты Android и iOS. Один и тот же код собирается в приложение для двух платформ, а разрабатывать можно уже зная React и экосистему npm.
1. Что такое React Native
React Native (RN) — фреймворк от Meta (бывший Facebook). Вместо браузерных div-ов используются нативные компоненты: View, Text, Image. JavaScript-код выполняется в отдельном потоке и общается с нативным слоем, поэтому приложение выглядит по-настоящему «родным».
| Сравнение | React Native | Flutter |
|---|---|---|
| Язык | JavaScript / TypeScript | Dart |
| UI-элементы | нативные компоненты системы | своя отрисовка движком |
| Порог входа для веб-разработчика | низкий | средний |
| Сообщество / библиотеки | огромное (npm) | растущее (pub.dev) |
// Первый экран на React Native
import { Text, View } from 'react-native';
function App() {
return <View><Text>Привет, React Native!</Text></View>;
}
export default App;2. Установка и настройка окружения
Для разработки нужны: Node.js (LTS), JDK 17+ и Android Studio (для Android) или Xcode (для iOS на Mac). Проще всего проверить окружение утилитой npx react-native doctor.
# Создание нового проекта
npx @react-native-community/cli@latest init TodoApp
cd TodoApp
# Запуск сборки и Metro-сервера
npm start # терминал 1: Metro-бандлер
npm run android # терминал 2: сборка и запуск на эмулятореНовый проект React Native создаётся на TypeScript по умолчанию — файлы лежат в папке src (или App.tsx в корне). Metro — это бандлер, который объединяет все модули в один JS-файл.
node -v.3. Проект и его структура
Как устроен типичный проект RN:
| Путь | Что делает |
|---|---|
App.tsx | корневой компонент приложения |
src/ | исходники: компоненты, экраны, сервисы |
android/ | нативный проект Gradle (Android) |
ios/ | нативный проект Xcode (iOS) |
package.json | зависимости и скрипты npm |
__tests__/ | тесты на Jest |
Развитие экранов идёт в src/screens, переиспользуемые элементы — в src/components, а логика работы с данными — в src/services.
// App.tsx — точка входа
import { SafeAreaView, Text } from 'react-native';
function App() {
return (
<SafeAreaView>
<Text style={{ fontSize: 20 }}>Моё приложение</Text>
</SafeAreaView>
);
}
export default App;SafeAreaView учитывает «вырез» под динамический остров и нижнюю полосу жестов — текст не уедет под системные панели.4. JSX и компоненты
Компонент — функция, возвращающая JSX (синтаксис похожий на HTML, но с фигурными скобками для выражений). Компоненты делятся на презентационные (отрисовка) и контейнерные (логика и данные).
// Компонент с props и State
import { useState } from 'react';
import { Button, Text, View } from 'react-native';
function Welcome({ name }: { name: string }) {
const [count, setCount] = useState(0);
return (
<View>
<Text>Привет, {name}!</Text>
<Text>Счёт: {count}</Text>
<Button title="Плюс" onPress={() => setCount(c => c + 1)} />
</View>
);
}
<Welcome name="Анна" />Хуки — функции для состояния: useState (локальное состояние), useEffect (побочные эффекты), useContext (общий контекст). Не вызывай их внутри циклов и условий.
View, Text, Image, TextInput, ScrollView — импортируются из пакета react-native.5. Стилизация: StyleSheet и Flexbox
Стили задаются объектами через StyleSheet.create. Свойства почти как в CSS, но в camelCase: backgroundColor, fontSize, borderRadius.
import { StyleSheet, Text, View } from 'react-native';
const styles = StyleSheet.create({
card: {
flex: 1,
padding: 16,
borderRadius: 8,
backgroundColor: '#f0f0f0',
margin: 8,
},
title: {
fontSize: 18,
fontWeight: 'bold',
},
});
function Card({ title }: { title: string }) {
return (
<View style={styles.card}>
<Text style={styles.title}>{title}</Text>
</View>
);
}| CSS (веб) | React Native |
|---|---|
div | View |
| селекторы и псевдоклассы | нет — только инлайн-объекты |
| display: flex (по умолчанию block) | flex — по умолчанию всегда |
| px / em / rem | числа без единиц (px) |
flexDirection — column, а не row как в CSS. Если колонка не выстраивается — проверь это свойство.6. Навигация между экранами
Стандарт — библиотека React Navigation: навигатор с фиксированной историей переходов поверх нативных контроллеров.
npm install @react-navigation/native
npm install @react-navigation/native-stack
npm install react-native-screens react-native-safe-area-contextconst Stack = createNativeStackNavigator();
function AppNavigator() {
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
);
}
// Переход внутри HomeScreen
navigation.navigate('Details', { id: 42 });Для нижнего меню используется @react-navigation/bottom-tabs. Данные между экранами передаются параметрами маршрута, которые читаются через route.params.
7. Работа с сетью
Запросы выполняются через встроенный fetch или axios. Ответ приходит как текст, который разбирается в JSON. Загруженные данные вынеси в useEffect.
import { useEffect, useState } from 'react';
import { FlatList, Text } from 'react-native';
function PostsScreen() {
const [posts, setPosts] = useState([]);
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/posts')
.then((res) => res.json())
.then(setPosts)
.catch((err) => console.error(err));
}, []);
return (
<FlatList
data={posts}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => <Text>{item.title}</Text>}
/>
);
}Элемент FlatList — ленивый список: он рендерит только видимые строки, что важно для больших данных.
8. Локальное хранилище: AsyncStorage
Простое персистентное хранилище «ключ-значение» — @react-native-async-storage/async-storage (аналог localStorage для мобильных устройств).
npm install @react-native-async-storage/async-storageimport AsyncStorage from '@react-native-async-storage/async-storage';
// Запись
await AsyncStorage.setItem('token', 'abc123');
await AsyncStorage.setItem('cart', JSON.stringify([1, 2, 3]));
// Чтение
const token = await AsyncStorage.getItem('token');
const cart = JSON.parse((await AsyncStorage.getItem('cart')) || '[]');
// Удаление
await AsyncStorage.removeItem('token');Храни в AsyncStorage настройки и кэш, а не секреты. Для структурированных данных с запросами подойдёт SQLite-обёртка react-native-sqlite-storage.
react-native-keychain — там применяется системное хранилище ключей.9. Экспресс-интро к Expo
Expo — платформа, которая ускоряет разработку: готовые инструменты сборки, облачные сервисы, тестирование прямо на телефоне через приложение Expo Go (сканирование QR-кода).
# Создание проекта Expo на TypeScript
npx create-expo-app@latest my-app
cd my-app
npm start # откроет QR-код для Expo Go| Критерий | React Native CLI | Expo |
|---|---|---|
| Быстрый старт | нужна полная настройка | работает за пару минут |
| Нативные модули | любые | только входящие в Expo SDK (или прекомпиляция) |
| Тестирование на устройстве | сборка и установка APK | скан QR в Expo Go |
Для отправки в магазины Expo делает облачную сборку: eas build --platform android, затем загрузка в Play Console. Свежие версии Expo поддерживают собственные нативные библиотеки через prebuild.
expo prebuild, когда понадобятся специфические нативные модули.10. Сборка и публикация
Android-апк собирается из папки android. Релизная сборка должна быть подписана ключом.
# Сборка релизного APK (в папке android)
gradlew assembleRelease
# Публикация на Android через Play Console
# 1. Подписать APK ключом .jks
# 2. Зарегистрировать аккаунт разработчика (~25$)
# 3. Загрузить сборку и заполнить карточкуДля iOS: Product → Archive в Xcode, затем Upload to App Store. Оба магазина имеют свои требования и модерацию — заранее готовь скриншоты, описание и политику конфиденциальности.
.jks — это потеря права обновлять приложение навсегда. Храни ключ и пароли вне репозитория, лучше в менеджере паролей.11. Отладка и инструменты
React Native профилирует и дебажит мягче, чем кажется:
# терминал разработки
npx react-native start # Metro
# на устройстве: DevMenu (жест shake)
# Reload, Debug JS Remotely, Show Perf Monitor- React DevTools — иерархия компонентов и хуки как в вебе;
- Flipper — логи, сеть, AsyncStorage одним окном;
- логгирование
console.logидёт в терминал Metro.
12. Push-уведомления и deep links
Для пушей в кросс-платформе стандарт — библиотека react-native-firebase или RNPushNotification:
// регистрация токена при старте
import messaging from "@react-native-firebase/messaging";
async function onAppStart() {
const token = await messaging().getToken();
await saveToken(token);
messaging().onMessage(showLocalNotification);
}Deep links открывают приложение по схеме myapp://order/42. Настройка в AndroidManifest и Info.plist, а реакция на ссылку — в линкере:
useEffect(() => {
Linking.addEventListener("url",
({ url }) => navigateToRoute(url));
}, []);13. Анимации: Animated и Reanimated
Встроенный Animated умеет плавно двигать props:
import { Animated, Easing } from "react-native";
const scale = useRef(new Animated.Value(1)).current;
const pulse = () =>
Animated.sequence([
Animated.timing(scale, { toValue: 1.1, duration: 120,
useNativeDriver: true }),
Animated.timing(scale, { toValue: 1, duration: 120,
useNativeDriver: true }),
]).start();
// в JSX: Animated.View style={{ transform: [{ scale }] }}Для сложных жестов бери react-native-reanimated + gesture-handler.
useNativeDriver: true— анимация идёт на стороне UI-потока;- следи, что анимируешь transform/opacity, а не layout-свойства;
- 60 fps важнее, чем безумные эффекты: замерь Perf Monitor.
14. Данные: React Query и Zustand
React Query управляет «серверными» данными: кэш, ретраи, инвалидация; Zustand — глобальным UI-состоянием.
import { useQuery } from "@tanstack/react-query";
const { data } = useQuery({
queryKey: ["orders"],
queryFn: listOrders,
staleTime: 60_000,
});import { create } from "zustand";
const useCart = create((set) => ({
items: [],
add: (p) => set((s) => ({ items: [...s.items, p] })),
}));Разделение обязанностей: серверные данные — в React Query, а локальная навигация и корзина — в Zustand. Состояние реже «разъезжается».
15. Тестирование: Jest, React Native Testing Library, Detox
Юнит-уровень даёт быстрое покрытие логики, E2E — реальные пользовательские сценарии:
// unit: рендер + поведение
import { render, fireEvent } from "@testing-library/react-native";
const { getByText } = render(<CartBadge />);
fireEvent.press(getByText("Add"));
// e2e (Detox)
await element(by.id("cart.open")).tap();
await expect(element(by.text("Корзина"))).toBeVisible();- моккай нативные модули, которые не нужны в тесте: AsyncStorage, NetInfo;
- Detox гоняет на симуляторе/эмуляторе — включай в CI для ключевого сценария;
- тест на API-слой отдельно от UI — дешевле и стабильнее.
16. Кастомные нативные модули и мосты
Иногда нужен код на платформе: биометрия, фоновая синхронизация, специфичное SDK. В React Native 0.71+ пишут нативную часть через Turbo Modules и Fabric:
// Kotlin (пример моста в RN
) {
module "Greeter" {
with exportAs "Greeter" {
@NitroModule
}
}
}// из JS всё тот же импорт
import { Greeter } from "react-native-greeter";
Greeter.greet("Иван");- держи нативную логику тонкой: тяжёлые вычисления — в JS;
- путь к платформенному файлу:
Platform.select; - обновление нока: добавление нового нативного модуля требует rebuild приложения.
17. Производительность: FlatList, memo, Hermes
Зависание списка или интерфейса = уход пользователя. Основные приёмы:
- FlatList вместо map: виртуализация рендерит только видимые строки;
- keyExtractor стабильный — иначе строки «прыгают»;
- оберни тяжёлые компоненты в
memoи не передавай анонимные функции в props; - Hermes (опционально включи в android/app/build.gradle) — меньше памяти и быстрый старт;
- изображения: resize заблаговременно, placeholder —
Imageсвойства для ширины/высоты.
const Item = memo(({ item, onPress }) => (
<Pressable onPress={onPress}><Text>{item.name}</Text></Pressable>
));useAnimatedStyle (Reanimated) работает в UI-потоке, а не через мост — плавнее при 60 fps.18. Безопасность и релиз
Мобильный релиз требует осторожности: от секретов до стора:
| Аспект | Рекомендация |
|---|---|
| секреты | .env + переменные сборки, никогда не коммить ключи |
| хранилище | токены в Keychain/Keystore (react-native-keychain) |
| сеть | HTTPS, сертификат-pinning для критичных API |
| версии | CodePush/OTA только для JS-обновлений, нативная — через апдейт стора |
| релиз | подписанный артефакт, production-build протестирован на смартфоне |
debug: JS-бандл и настройки разработчика могут протекчь. Собирай release с минификацией.19. Тёмная тема и дизайн-токены
Системная тёмная тема — ожидаемое поведение: большинство пользователей включает её на уровне ОС. Учитывай её с первого дня:
import { useColorScheme } from "react-native";
const colors = useColorScheme() === "dark"
? { bg: "#121212", text: "#eee", accent: "#7c7cff" }
: { bg: "#ffffff", text: "#111", accent: "#3b3bff" };
// стили строятся из токена, а не из магических значений
<View style={{ backgroundColor: colors.bg }}>
<Text style={{ color: colors.text }}>Контент</Text>
</View>- токены (цвета, отступы, радиусы) держи в одном объекте-теме, а не вразброс по файлам;
- контраст проверяй на реальном устройстве: светлая и тёмная темы читаются по-разному;
- картинки с прозрачностью проси у дизайнера в обеих версиях — белая подложка ломает тему.
20. Формы и работа с клавиатурой
Форма на телефоне — испытание на терпение: клавиатура закрывает поля, автокапитализация мешает, скачок layout сбивает ввод. Базовый каркас:
import { KeyboardAvoidingView, TextInput, Platform } from "react-native";
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
keyboardVerticalOffset={80}
>
<TextInput
placeholder="Email"
autoCapitalize="none"
keyboardType="email-address"
returnKeyType="next"
/>
</KeyboardAvoidingView>- переход к следующему полю через returnKeyType — на iOS клавиатура прячется и открывается целенаправленно;
- коррекция и автокапитализация отключаются там, где мешают: email, коды, никнеймы;
- шапка и кнопка отправки — выше клавиатуры, иначе пользователь их не видит;
- валидация —
react-hook-form+ zod (см. курс React): та же схема, что и в вебе.
21. AppState, фоновые задачи и синхронизация
JS-таймеры в React Native засыпают, когда приложение уходит в фон. Если синхронизация завязана на setInterval, после «сонного» устройства данные окажутся устаревшими:
import { AppState } from "react-native";
useEffect(() => {
const sub = AppState.addEventListener("change", (state) => {
if (state === "active") refreshData(); // свежее при возврате
if (state === "background") clearTimers(); // тяжёлое — вон из фона
});
return () => sub.remove();
}, []);- загрузку в фоне ограничивают ОС: iOS даёт секунды, Android — короткое окно задач;
- «долгие» фоновые обновления — дело нативных модулей (BGTask, WorkManager), а не JS;
- сетевые эффекты при возврате в foreground: проверяй, что данные не протухли.
22. Продакшен-мониторинг: крашлити и аналитика
Релиз без мониторинга — потерянная обратная связь: падения видны только в отзывах. Минимум для производства:
// Sentry — крашлити и производительность в одном окне
import * as Sentry from "@sentry/react-native";
Sentry.init({
dsn: "https://...@sentry.io/...",
tracesSampleRate: 0.2,
});
// ErrorBoundary сохраняет экран, а не только пишет лог
class AppBoundary extends React.Component {
componentDidCatch(e) { Sentry.captureException(e); }
}- логи падений — Sentry или Firebase Crashlytics: стек, устройство, версия ОС;
- analytics-события и карта экранов — отдельный инструмент, не путать с крашлити;
- метрики производительности (TTI, сетевые вызовы) — в той же панели, что и краши;
- алерты на новые релизы: рост падений после обновления виден сразу.
Что дальше
Закрепи базовый React: курс React объяснит виртуальный DOM, хуки и структуру компонентов. Для API-запросов изучи REST API, для стилей — CSS и Flexbox. Конфигурация форматеров и статического анализа — ESLint, а если хочется кроссплатформенности без нативного кода — смотри Flutter; нативные подходы — Android (Kotlin) и iOS (Swift).