React Native — мобильные приложения на JavaScript

React Native позволяет писать настоящие мобильные приложения на JavaScript и TypeScript, используя нативные компоненты Android и iOS. Один и тот же код собирается в приложение для двух платформ, а разрабатывать можно уже зная React и экосистему npm.

Уровень: средний 18 разделов 2–3 часа

1. Что такое React Native

React Native (RN) — фреймворк от Meta (бывший Facebook). Вместо браузерных div-ов используются нативные компоненты: View, Text, Image. JavaScript-код выполняется в отдельном потоке и общается с нативным слоем, поэтому приложение выглядит по-настоящему «родным».

СравнениеReact NativeFlutter
ЯзыкJavaScript / TypeScriptDart
UI-элементынативные компоненты системысвоя отрисовка движком
Порог входа для веб-разработчиканизкийсредний
Сообщество / библиотекиогромное (npm)растущее (pub.dev)
// Первый экран на React Native
import { Text, View } from 'react-native';

function App() {
  return <View><Text>Привет, React Native!</Text></View>;
}

export default App;
Если ты знаешь React — ты уже знаешь 80% React Native. Разница в стилях, навигации и отсутствии HTML-тегов.

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.js старой версии не подойдёт: используй LTS (18 или 20+). Проверяется командой 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
divView
селекторы и псевдоклассынет — только инлайн-объекты
display: flex (по умолчанию block)flex — по умолчанию всегда
px / em / remчисла без единиц (px)
В RN default-направление 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-context
const 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.

Sync-инициализация NavigationContainer нужна один раз в корне приложения — оберни в него весь навигационный стек приложения.

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 — ленивый список: он рендерит только видимые строки, что важно для больших данных.

Не забудь ловить ошибки сети и показывать состояние загрузки (индикатор или скелетон) — пользователь должен понимать, что происходит.
Для продакшена используй axios с перехватчиками: к ним удобно вешать заголовок токена авторизации в одном месте.

8. Локальное хранилище: AsyncStorage

Простое персистентное хранилище «ключ-значение» — @react-native-async-storage/async-storage (аналог localStorage для мобильных устройств).

npm install @react-native-async-storage/async-storage
import 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.

AsyncStorage не шифрован. Для чувствительных данных (токены, пароли) используй 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 CLIExpo
Быстрый стартнужна полная настройкаработает за пару минут
Нативные модулилюбыетолько входящие в Expo SDK (или прекомпиляция)
Тестирование на устройствесборка и установка APKскан QR в Expo Go

Для отправки в магазины Expo делает облачную сборку: eas build --platform android, затем загрузка в Play Console. Свежие версии Expo поддерживают собственные нативные библиотеки через prebuild.

Начни с Expo — он снимет всю боль настройки окружения. Перейти на чистый CLI можно позже, выйдя через 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 — это потеря права обновлять приложение навсегда. Храни ключ и пароли вне репозитория, лучше в менеджере паролей.
Перед релизом прогони ESLint, а в магазин загружай кранч-версию медиа и файлов (aab постарайся) — так сборка меньше и одобрение быстрее.

11. Отладка и инструменты

React Native профилирует и дебажит мягче, чем кажется:

# терминал разработки
npx react-native start   # Metro

# на устройстве: DevMenu (жест shake)
# Reload, Debug JS Remotely, Show Perf Monitor
«Debug JS Remotely» заметно замедляет анимации: не суди о перформансе в этом режиме. Используй Perf Monitor на on-device релизе.

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));
}, []);
Пуш без deep link смертен: всегда давай кнопку действия, ведущую к конкретному экрану.

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.

Animated значения держатся в ref, а не в state, чтобы не пересобирать рендер на каждый кадр.

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. Состояние реже «разъезжается».

Не дублируй выборки в каждом экране: вынеси query в hooks и переиспользуй, включив staleTime под реальную частоту обновления.

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();
Начинай с теста на «корзину» и логин: они покрывают большинство регрессий оплаты и сетапов.

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("Иван");
Не падай всякий раз, когда нативная сторона не снята: сначала простой мост, потом TurboModule — усложняй по необходимости.

17. Производительность: FlatList, memo, Hermes

Зависание списка или интерфейса = уход пользователя. Основные приёмы:

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 протестирован на смартфоне
Не публикуй 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>
Не прячь кнопку отправки за клавиатурой; лучше закрепи её поверх поля ввода. «Мёртвая» кнопка вне видимости — главный источник брошенных заявок.

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();
}, []);
Реальный кейс: мессенджер без синхронизации при возврате показывает «старое» состояние беседы. Обновление по AppState закрывает половину жалоб на зависшие данные.

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); }
}
Настрой распределение падений по версиям приложения: одна строка в дэшборде подскажет, какой релиз что сломал, ещё до ответов пользователей.

Что дальше

Закрепи базовый React: курс React объяснит виртуальный DOM, хуки и структуру компонентов. Для API-запросов изучи REST API, для стилей — CSS и Flexbox. Конфигурация форматеров и статического анализа — ESLint, а если хочется кроссплатформенности без нативного кода — смотри Flutter; нативные подходы — Android (Kotlin) и iOS (Swift).

← Вернуться к курсам