Навигация

React Native для React-разработчиков

React Native позволяет писать мобильные приложения для iOS и Android на React, но рендерить их в настоящие нативные компоненты — не WebView и не кросс-платформенный HTML. Статья для тех, кто знает React и хочет понять, что переносится напрямую, что меняется и как устроен рантайм под капотом. Разбираем архитектуру (Bridge → JSI + Fabric), основные компоненты, стилизацию, навигацию через Expo Router и выбор между Expo и Bare CLI.

Содержание

  1. Что такое React Native
  2. Как работает React Native
  3. React Native vs React
  4. Новая архитектура (Fabric + TurboModules)
  5. Основные компоненты
  6. Стилизация
  7. Навигация
  8. Состояние и данные
  9. Нативные модули
  10. Expo vs Bare CLI
  11. Отладка
  12. Публикация в сторах
  13. Когда НЕ выбирать React Native
  14. Ключевые тезисы для интервью
  15. Заключение
  16. Полезные ссылки

Что такое React Native

React Native — фреймворк для создания нативных мобильных приложений (iOS и Android) с использованием React. Код пишется на JavaScript/TypeScript, но рендеринг происходит в нативные UI-компоненты платформы — не WebView, не кросс-платформенный HTML/CSS.

React (Web) → ReactDOM → HTML / CSS / DOM
React Native → Native Renderer → UIKit (iOS) / Android Views

Ключевые особенности:

  • Один код для iOS и Android (с нативными исключениями)
  • Нативные компоненты — не WebView, настоящие UIView, android.widget
  • Hot Reload — мгновенное обновление без пересборки
  • Нативные модули — доступ к камере, GPS, push-уведомлениям, Bluetooth
  • Expo — managed workflow без необходимости Xcode / Android Studio

💡 В 2026 году React Native используется в production в приложениях Meta (Facebook, Instagram), Shopify, Discord, Coinbase, Microsoft Office Mobile и тысячах других.


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

Архитектура (старая — Bridge)

┌──────────────────┐          ┌──────────────────┐
│  JavaScript      │          │  Native          │
│  Thread          │          │  Thread          │
│                  │  Bridge  │                  │
│  React           │◄────────►│  UIKit / Views   │
│  Components      │  (JSON)  │  Native Modules  │
│                  │          │                  │
└──────────────────┘          └──────────────────┘

JavaScript-поток вычисляет дерево компонентов, сериализует его в JSON и отправляет через асинхронный «мост» (Bridge) в нативный поток. Нативный поток десериализует и рендерит реальные UI-компоненты.

Проблема: Bridge — узкое место. Большие объёмы данных (скролл, жесты, анимации) вызывают задержки.

Архитектура (новая — JSI)

┌──────────────────┐          ┌──────────────────┐
│  JavaScript      │          │  Native          │
│  (Hermes)        │◄────────►│  (Fabric +       │
│                  │   JSI    │   TurboModules)  │
│  React           │ (C++)    │                  │
│  Components      │          │                  │
└──────────────────┘          └──────────────────┘

JSI (JavaScript Interface) — C++ интерфейс, позволяющий JavaScript напрямую вызывать нативные методы без сериализации. Это убирает bottleneck Bridge и открывает путь к синхронным вызовам.


React Native vs React

React (Web)React Native
РендерингHTML + CSS + DOMНативные UI-компоненты
СтилизацияCSS / CSS-in-JS / TailwindStyleSheet (подмножество CSS)
НавигацияReact RouterReact Navigation / Expo Router
СобытияonClick, onChangeonPress, onChangeText
АнимацииCSS transitions / Framer MotionAnimated, react-native-reanimated
Списки<ul> / map()<FlatList>, <SectionList>
Формы<form>, <input><TextInput>, библиотеки форм
ОтладкаChrome DevToolsFlipper, React DevTools, Metro
ДеплойVercel / NetlifyApp Store / Google Play
Hot ReloadVite HMRFast Refresh

Общие концепции

Всё, что вы знаете из React, работает:

  • Компоненты, JSX, пропсы, состояние
  • Хуки (useState, useEffect, useRef, useMemo, useCallback)
  • Context API
  • Custom Hooks
  • Zustand, Jotai, TanStack Query
  • TypeScript

Ключевые различия

// React (Web)
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
    </div>
  );
}

// React Native
import { useState } from "react";
import { View, Text, Button, StyleSheet } from "react-native";

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Count: {count}</Text>
      <Button title="Increment" onPress={() => setCount(c => c + 1)} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: "center", alignItems: "center" },
  title: { fontSize: 24, fontWeight: "bold" },
});

Новая архитектура (Fabric + TurboModules)

React Native постепенно переходит на новую архитектуру:

Fabric (новый рендерер)

  • Синхронный рендеринг (приоритетные обновления, как Concurrent React)
  • Прямое взаимодействие с нативным UI через JSI
  • Поддержка Suspense и concurrent features

TurboModules (нативные модули)

  • Ленивая загрузка нативных модулей (не все при старте)
  • Прямые вызовы через JSI (без Bridge)
  • Типобезопасность через CodeGen (автоматическая генерация типов)

CodeGen

// NativeSpec.json
{
  "nativeModules": {
    "Geolocation": {
      "methods": {
        "getCurrentPosition": {
          "promise": true,
          "params": []
        }
      }
    }
  }
}

CodeGen генерирует типобезопасные обёртки для JavaScript и нативного кода.


Основные компоненты

View и Text

import { View, Text, StyleSheet } from "react-native";

function Card({ title, description }) {
  return (
    <View style={styles.card}>
      <Text style={styles.title}>{title}</Text>
      <Text style={styles.description}>{description}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  card: { padding: 16, borderRadius: 8, backgroundColor: "#fff" },
  title: { fontSize: 18, fontWeight: "bold" },
  description: { fontSize: 14, color: "#666", marginTop: 4 },
});

TouchableOpacity и Button

import { TouchableOpacity, Button, Alert } from "react-native";

function Actions() {
  return (
    <View>
      <TouchableOpacity onPress={() => Alert.alert("Tapped!")}>
        <Text>Custom Button</Text>
      </TouchableOpacity>

      <Button title="Default Button" onPress={() => {}} />
    </View>
  );
}

TextInput

function SearchBar() {
  const [query, setQuery] = useState("");

  return (
    <TextInput
      style={styles.input}
      placeholder="Search..."
      value={query}
      onChangeText={setQuery}
      autoCapitalize="none"
      autoCorrect={false}
    />
  );
}

FlatList (виртуализированный список)

function UserList({ users }) {
  return (
    <FlatList
      data={users}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <View style={styles.row}>
          <Text>{item.name}</Text>
          <Text>{item.email}</Text>
        </View>
      )}
      refreshing={isRefreshing}
      onRefresh={handleRefresh}
      onEndReached={handleLoadMore}
      onEndReachedThreshold={0.5}
    />
  );
}

ScrollView

function FormScreen() {
  return (
    <ScrollView contentContainerStyle={styles.container}>
      <TextInput placeholder="Name" />
      <TextInput placeholder="Email" keyboardType="email-address" />
      <Button title="Submit" onPress={handleSubmit} />
    </ScrollView>
  );
}

Image

<Image
  source={{ uri: "https://example.com/image.jpg" }}
  style={{ width: 200, height: 200 }}
  resizeMode="cover"
/>

// Локальное изображение
<Image source={require("./assets/logo.png")} style={{ width: 100, height: 100 }} />

SafeAreaView и StatusBar

import { SafeAreaView, StatusBar } from "react-native";

function App() {
  return (
    <SafeAreaView style={styles.container}>
      <StatusBar barStyle="dark-content" />
      <MainContent />
    </SafeAreaView>
  );
}

Стилизация

StyleSheet

import { StyleSheet } from "react-native";

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16,
    backgroundColor: "#f5f5f5",
  },
  card: {
    padding: 16,
    borderRadius: 8,
    backgroundColor: "#fff",
    shadowColor: "#000",
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
});

Flexbox (отличия от CSS)

React Native использует Flexbox, но с некоторыми отличиями:

  • flexDirection по умолчанию — column (не row как в CSS)
  • Нет display: grid, float, position: relative
  • position поддерживает только relative и absolute
  • Нет shorthand-свойств (margin: 10px 20px не работает)
<View style={{
  flex: 1,
  flexDirection: "row",
  justifyContent: "space-between",
  alignItems: "center",
}}>
  <View style={{ width: 50, height: 50, backgroundColor: "red" }} />
  <View style={{ width: 50, height: 50, backgroundColor: "blue" }} />
</View>

Responsive design

import { Dimensions, useWindowDimensions } from "react-native";

function ResponsiveLayout() {
  const { width, height } = useWindowDimensions();
  const isPortrait = height > width;

  return (
    <View style={{
      flexDirection: isPortrait ? "column" : "row",
      flex: 1,
    }}>
      <Sidebar />
      <Content />
    </View>
  );
}

Styled Components в React Native

import styled from "styled-components/native";

const Container = styled.View`
  flex: 1;
  justify-content: center;
  align-items: center;
  background-color: #f5f5f5;
`;

const Title = styled.Text`
  font-size: 24px;
  font-weight: bold;
  color: #333;
`;

function Screen() {
  return (
    <Container>
      <Title>Hello, React Native!</Title>
    </Container>
  );
}

NativeWind (Tailwind для React Native)

import { View, Text } from "react-native";

function Card() {
  return (
    <View className="p-4 bg-white rounded-lg shadow-md">
      <Text className="text-lg font-bold text-gray-800">Title</Text>
      <Text className="text-sm text-gray-500 mt-1">Description</Text>
    </View>
  );
}

Навигация

Expo Router (рекомендуемый)

Expo Router — файловая маршрутизация, аналог Next.js App Router:

app/
├── _layout.tsx       # корневой layout
├── index.tsx         # главная страница (/)
├── (tabs)/
│   ├── _layout.tsx   # tab layout
│   ├── home.tsx      # вкладка "Home"
│   └── profile.tsx   # вкладка "Profile"
├── (auth)/
│   ├── login.tsx     # /login
│   └── register.tsx  # /register
└── settings/
    └── index.tsx     # /settings
// app/_layout.tsx
import { Stack } from "expo-router";

export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      <Stack.Screen name="(auth)" options={{ headerShown: false }} />
    </Stack>
  );
}

// app/(tabs)/_layout.tsx
import { Tabs } from "expo-router";

export default function TabLayout() {
  return (
    <Tabs>
      <Tabs.Screen name="home" options={{ title: "Home", tabBarIcon: HomeIcon }} />
      <Tabs.Screen name="profile" options={{ title: "Profile", tabBarIcon: ProfileIcon }} />
    </Tabs>
  );
}

React Navigation

import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";

const Stack = createNativeStackNavigator();

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Details" component={DetailsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

function HomeScreen({ navigation }) {
  return (
    <View>
      <Text>Home Screen</Text>
      <Button title="Go to Details" onPress={() => navigation.navigate("Details")} />
    </View>
  );
}

Состояние и данные

Локальное состояние

import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);
  return <Button title={`Count: ${count}`} onPress={() => setCount(c => c + 1)} />;
}

AsyncStorage

import AsyncStorage from "@react-native-async-storage/async-storage";

async function saveUser(user) {
  await AsyncStorage.setItem("user", JSON.stringify(user));
}

async function loadUser() {
  const json = await AsyncStorage.getItem("user");
  return json ? JSON.parse(json) : null;
}

Zustand (работает как в React)

import { create } from "zustand";

const useAuthStore = create((set) => ({
  user: null,
  login: (user) => set({ user }),
  logout: () => set({ user: null }),
}));

function Profile() {
  const { user, logout } = useAuthStore();
  return (
    <View>
      <Text>{user?.name}</Text>
      <Button title="Logout" onPress={logout} />
    </View>
  );
}

TanStack Query (работает как в React)

import { useQuery } from "@tanstack/react-query";

function UserList() {
  const { data, isLoading } = useQuery({
    queryKey: ["users"],
    queryFn: () => fetch("https://api.example.com/users").then((r) => r.json()),
  });

  if (isLoading) return <ActivityIndicator />;

  return (
    <FlatList
      data={data}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => <Text>{item.name}</Text>}
    />
  );
}

Нативные модули

Доступ к камере

import * as ImagePicker from "expo-image-picker";

async function pickImage() {
  const permission = await ImagePicker.requestMediaLibraryPermissionsAsync();
  if (!permission.granted) return;

  const result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: ["images"],
    quality: 0.8,
  });

  if (!result.canceled) {
    console.log("Selected:", result.assets[0].uri);
  }
}

Push-уведомления

import * as Notifications from "expo-notifications";

async function registerForPushNotifications() {
  const { status } = await Notifications.requestPermissionsAsync();
  if (status !== "granted") return;

  const token = await Notifications.getExpoPushTokenAsync({
    projectId: "your-project-id",
  });

  console.log("Push token:", token.data);
}

Notifications.addNotificationReceivedListener((notification) => {
  console.log("Notification received:", notification.request.content.title);
});

Геолокация

import * as Location from "expo-location";

async function getCurrentLocation() {
  const { status } = await Location.requestForegroundPermissionsAsync();
  if (status !== "granted") return;

  const location = await Location.getCurrentPositionAsync({});
  return {
    latitude: location.coords.latitude,
    longitude: location.coords.longitude,
  };
}

Expo vs Bare CLI

Expo (рекомендуется)

npx create-expo-app my-app
cd my-app
npx expo start

Плюсы:

  • Не нужен Xcode / Android Studio для разработки
  • EAS Build — облачная сборка для iOS/Android
  • EAS Update — OTA-обновления без ревью сторов
  • Готовые модули (камера, геолокация, уведомления)
  • Expo Router — файловая маршрутизация

Минусы:

  • Ограничения при использовании кастомных нативных модулей
  • Expo Dev Client нужен для нативного кода

Bare CLI (React Native CLI)

npx @react-native-community/cli init my-app

Плюсы:

  • Полный контроль над нативным кодом
  • Нет ограничений Expo

Минусы:

  • Нужен Xcode (macOS) для iOS
  • Нужен Android Studio для Android
  • Ручная настройка нативных модулей

💡 В 2026 году Expo — рекомендуемый способ начать React Native проект. Даже если нужен нативный код, Expo C API и Config Plugins позволяют добавлять нативные модули без «eject».


Отладка

React DevTools

npx react-devtools

Инспектирование дерева компонентов, пропсов, состояния — как в браузере.

Flipper

Нативная отладка:

  • Просмотр нативного UI-дерева
  • Сетевые запросы
  • База данных (SQLite)
  • Логи

Console.log и Metro

npx expo start --dev-client

Metro bundler показывает логи в терминале.

Debugging в VS Code

// .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug Expo App",
      "type": "exponent",
      "request": "launch"
    }
  ]
}

Публикация в сторах

EAS Build (Expo)

npm install -g eas-cli

eas login
eas build:configure

# iOS
eas build --platform ios --profile production

# Android
eas build --platform android --profile production

eas.json

{
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal"
    },
    "preview": {
      "distribution": "internal"
    },
    "production": {
      "autoIncrement": true
    }
  },
  "submit": {
    "production": {
      "ios": {
        "appleId": "your@email.com",
        "ascAppId": "123456789"
      },
      "android": {
        "serviceAccountKeyPath": "./google-services.json",
        "track": "production"
      }
    }
  }
}

EAS Update (OTA)

eas update --branch production --message "Fix login bug"

Обновления JavaScript-кода без ревью App Store / Google Play.


Когда НЕ выбирать React Native

React Native не подходит, если:

  • Тяжёлые анимации / игры — используйте Unity, Flutter или нативные фреймворки
  • AR / VR — нативные SDK (ARKit, ARCore) дают больше контроля
  • Bluetooth / IoT — нативные модули существуют, но отладка сложнее
  • Критическая производительность — нативный код быстрее (хотя новая архитектура сократила разрыв)
  • Одно приложение — если не планируете iOS + Android, нативная разработка проще

Ключевые тезисы для интервью

  • React Native рендерит настоящие нативные компоненты (UIView, android.widget) — это не WebView и не HTML.
  • Старая архитектура использовала асинхронный Bridge с JSON-сериализацией; новая — JSI + Fabric + TurboModules с прямыми C++-вызовами.
  • Все базовые концепции React (хуки, Context, custom hooks, Zustand, TanStack Query, TS) работают в React Native без изменений.
  • Основные примитивы UI: View, Text, TextInput, Button/TouchableOpacity, Image, ScrollView, FlatList, SafeAreaView.
  • Стилизация — StyleSheet.create с подмножеством CSS: flexDirection по умолчанию column, нет grid/float, нет shorthand для margin.
  • FlatList — виртуализированный список, обязательный для длинных данных; поддерживает pull-to-refresh и infinite scroll.
  • Навигация: Expo Router даёт файловую маршрутизацию (аналог Next.js App Router), React Navigation — императивный API.
  • AsyncStorage — key-value хранилище для локальных данных; аналог localStorage, но асинхронное.
  • Expo рекомендован как стартовая точка: EAS Build (облачная сборка), EAS Update (OTA без ревью сторов), готовые модули для камеры, push, геолокации.
  • React Native не подходит для тяжёлых 3D/игр, AR/VR и глубокой работы с Bluetooth/IoT — там лучше нативные SDK.

Заключение

Для React-разработчика вход в React Native — это в первую очередь смена набора компонентов и стилей: div → View, span → Text, onClick → onPress, CSS → StyleSheet. Логика, хуки, управление состоянием и запросы остаются теми же. Ключевое архитектурное знание — переход от Bridge к JSI, Fabric и TurboModules, который убирает главное узкое место старого рантайма. На практике почти всегда стоит начинать с Expo и EAS — они убирают Xcode/Android Studio из цикла разработки и дают OTA-обновления. Выбирайте нативную разработку тогда, когда упираетесь в 3D, AR/VR или сложную работу с железом.

Полезные ссылки