TL;DR
React Native 让你用 JavaScript 和 React 构建原生 iOS 和 Android 应用。两端代码复用率可达 90%,同时使用真正的原生组件。用 Expo 快速原型开发,或用裸工作流获得完全的原生控制。React Navigation 处理路由;Zustand 或 Context 处理状态。新架构(JSI + Fabric)在 2026 年带来了显著的性能提升。
Key Takeaways
- React Native renders true native components, not WebViews — resulting in near-native performance
- Expo SDK 52+ is the recommended starting point for most projects in 2026
- New Architecture (JSI + Fabric) is enabled by default and eliminates bridge bottlenecks
- FlatList is preferred over ScrollView for long lists; always set
keyExtractor - React Navigation 7 supports typed routes and deep linking with minimal configuration
- Hermes JavaScript engine is the default and dramatically improves startup time and memory
- EAS Build replaces manual Xcode/Gradle builds for Expo users
1. What Is React Native?
React Native is an open-source framework created by Meta that lets you build mobile applications for iOS and Android using JavaScript and React. Unlike hybrid frameworks that wrap a WebView, React Native translates your JavaScript components into actual native UI elements — the same ones used by apps written in Swift, Objective-C, Kotlin, or Java.
When you write <View> in React Native, it becomes a UIView on iOS and an android.view.View on Android. This is the fundamental difference from Ionic or Cordova, which render HTML inside a WebView. The result is a look, feel, and performance that matches platform conventions.
Native Components vs WebView
React Native maps each component to its native counterpart at the platform level. The JavaScript code runs on a separate JS thread, communicates with the native layer, and instructs it to render. With the New Architecture, this communication uses JSI (JavaScript Interface) — a C++ binding that enables direct synchronous calls, eliminating the old asynchronous message bridge that was a known bottleneck.
The New Architecture (2024–2026)
React Native's New Architecture consists of three main pillars:
- JSI (JavaScript Interface) — replaces the old bridge; allows JS to hold direct references to native objects and call native methods synchronously
- Fabric — a re-implementation of the UI rendering layer; React's reconciler runs on native threads, enabling synchronous layout and concurrent features
- TurboModules — lazy-loaded native modules that are initialized only when first used, reducing startup time
As of React Native 0.74+, the New Architecture is enabled by default for new projects. Existing projects should migrate using the official migration guide.
// Check your React Native version and New Architecture status
// In react-native.config.js or package.json
// package.json
{
"dependencies": {
"react-native": "0.76.5", // New Architecture default in 0.74+
"react": "18.3.1"
}
}
// android/gradle.properties — enable New Architecture
newArchEnabled=true
// iOS: set in Podfile
# Enable New Architecture (default true for new projects)
ENV['RCT_NEW_ARCH_ENABLED'] = '1'2. Core Components
React Native ships with a set of built-in components that map to native platform UI elements. These are the building blocks every React Native developer must know.
View and Text
View is the fundamental building block, equivalent to <div> in web development. Text is the only way to render text — you cannot place raw strings directly inside a View.
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
export default function Card({ title, subtitle }: { title: string; subtitle: string }) {
return (
<View style={styles.card}>
<Text style={styles.title}>{title}</Text>
<Text style={styles.subtitle}>{subtitle}</Text>
</View>
);
}
const styles = StyleSheet.create({
card: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3, // Android shadow
},
title: {
fontSize: 18,
fontWeight: '700',
color: '#1a1a1a',
marginBottom: 4,
},
subtitle: {
fontSize: 14,
color: '#6b7280',
lineHeight: 20,
},
});Image and ScrollView
import { Image, ScrollView, Dimensions } from 'react-native';
const { width } = Dimensions.get('window');
// Local image (bundled with app)
<Image source={require('./assets/logo.png')} style={{ width: 100, height: 100 }} />
// Remote image — requires explicit width/height
<Image
source={{ uri: 'https://example.com/photo.jpg' }}
style={{ width: width - 32, height: 200, borderRadius: 8 }}
resizeMode="cover"
/>
// ScrollView — good for small lists or mixed content
// WARNING: renders all children at once; use FlatList for long lists
<ScrollView
contentContainerStyle={{ padding: 16 }}
showsVerticalScrollIndicator={false}
keyboardShouldPersistTaps="handled"
>
{items.map(item => <Card key={item.id} {...item} />)}
</ScrollView>FlatList — Virtualized Lists
FlatList is the go-to component for long data sets. It only renders items currently visible on screen (windowing), dramatically reducing memory usage and improving scroll performance.
import { FlatList, View, Text, StyleSheet } from 'react-native';
type User = { id: string; name: string; email: string };
export default function UserList({ users }: { users: User[] }) {
const renderItem = ({ item }: { item: User }) => (
<View style={styles.row}>
<Text style={styles.name}>{item.name}</Text>
<Text style={styles.email}>{item.email}</Text>
</View>
);
return (
<FlatList
data={users}
renderItem={renderItem}
keyExtractor={(item) => item.id} // Stable unique key
ItemSeparatorComponent={() => <View style={styles.separator} />}
ListEmptyComponent={<Text>No users found</Text>}
ListHeaderComponent={<Text style={styles.header}>Team Members</Text>}
getItemLayout={(_, index) => ({ // Optimization: skip layout calculation
length: 72,
offset: 72 * index,
index,
})}
initialNumToRender={10}
maxToRenderPerBatch={10}
windowSize={5}
removeClippedSubviews={true} // Android: recycle off-screen views
/>
);
}
const styles = StyleSheet.create({
row: { padding: 16, backgroundColor: '#fff' },
name: { fontSize: 16, fontWeight: '600' },
email: { fontSize: 13, color: '#6b7280', marginTop: 2 },
separator: { height: 1, backgroundColor: '#f3f4f6' },
header: { fontSize: 20, fontWeight: '700', padding: 16 },
});TouchableOpacity and Pressable
import { TouchableOpacity, Pressable, Text } from 'react-native';
// TouchableOpacity — reduces opacity on press (classic approach)
<TouchableOpacity
onPress={() => console.log('pressed')}
activeOpacity={0.7}
style={{ padding: 12, backgroundColor: '#3b82f6', borderRadius: 8 }}
>
<Text style={{ color: '#fff', fontWeight: '600' }}>Submit</Text>
</TouchableOpacity>
// Pressable — more flexible, supports pressed state styling (recommended)
<Pressable
onPress={handlePress}
onLongPress={handleLongPress}
style={({ pressed }) => ({
padding: 12,
backgroundColor: pressed ? '#2563eb' : '#3b82f6',
borderRadius: 8,
transform: [{ scale: pressed ? 0.97 : 1 }],
})}
>
{({ pressed }) => (
<Text style={{ color: pressed ? '#e0f2fe' : '#fff', fontWeight: '600' }}>
{pressed ? 'Submitting...' : 'Submit'}
</Text>
)}
</Pressable>3. Expo vs Bare React Native
One of the first decisions you'll make is whether to use Expo or plain React Native. Both are valid choices with different trade-offs.
Expo Managed Workflow
Expo abstracts away iOS and Android native build tooling. You write JavaScript/TypeScript and Expo handles the rest: code signing, certificates, build servers, OTA updates, and 50+ built-in modules (camera, notifications, location, sensors, etc.). You do not need a Mac to build iOS apps — EAS Build runs Xcode on Expo's cloud infrastructure.
# Create a new Expo project (recommended for most developers)
npx create-expo-app MyApp --template blank-typescript
cd MyApp
# Start development server
npx expo start
# Run on iOS simulator
npx expo run:ios
# Run on Android emulator
npx expo run:android
# Install Expo modules
npx expo install expo-camera expo-location expo-notifications
# Build for production with EAS
npx eas build --platform ios --profile production
npx eas build --platform android --profile production
# Submit to stores
npx eas submit --platform ios
npx eas submit --platform androidBare React Native Workflow
The bare workflow gives you full access to the native ios/ and android/ project directories. You can write native modules in Swift and Kotlin, integrate any third-party SDK, and have complete control over every build setting. The trade-off is more complexity and the need for Xcode on macOS for iOS builds.
# Create bare React Native project
npx react-native@latest init MyApp --template react-native-template-typescript
cd MyApp
# iOS (requires macOS + Xcode)
cd ios && pod install && cd ..
npx react-native run-ios
# Android (requires Android Studio + SDK)
npx react-native run-android
# Expo bare workflow — starts managed, ejects to bare
npx create-expo-app MyApp
cd MyApp
npx expo prebuild # Generates ios/ and android/ directories
npx expo run:ios
npx expo run:androidWhen to Use Each
| Scenario | Recommended |
|---|---|
| New project, standard features | Expo Managed |
| Need custom native code (Swift/Kotlin) | Bare / Expo Bare |
| No Mac available, build iOS | Expo EAS Build |
| OTA updates without App Store review | Expo EAS Update |
| Integrating legacy native SDKs | Bare React Native |
| File-based routing (like Next.js) | Expo Router (SDK 50+) |
4. Navigation with React Navigation
React Navigation is the de facto standard for navigation in React Native. It provides Stack, Tab, Drawer navigators and supports deep linking, typed routes, and animations that feel native on both platforms.
Installation and Setup
# Core library + native dependencies
npm install @react-navigation/native
npx expo install react-native-screens react-native-safe-area-context
# Stack Navigator (most common)
npm install @react-navigation/native-stack
# Bottom Tab Navigator
npm install @react-navigation/bottom-tabs
# Drawer Navigator
npm install @react-navigation/drawer
npx expo install react-native-gesture-handler react-native-reanimatedStack Navigation
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { useNavigation, useRoute } from '@react-navigation/native';
// Define your param types for TypeScript
type RootStackParamList = {
Home: undefined;
UserDetail: { userId: string; userName: string };
Settings: undefined;
};
const Stack = createNativeStackNavigator<RootStackParamList>();
// App entry point
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName="Home"
screenOptions={{
headerStyle: { backgroundColor: '#3b82f6' },
headerTintColor: '#fff',
headerTitleStyle: { fontWeight: '700' },
animation: 'slide_from_right',
}}
>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen
name="UserDetail"
component={UserDetailScreen}
options={({ route }) => ({ title: route.params.userName })}
/>
<Stack.Screen name="Settings" component={SettingsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
// Navigate from a screen
function HomeScreen() {
const navigation = useNavigation();
return (
<Pressable
onPress={() =>
navigation.navigate('UserDetail', {
userId: 'user_123',
userName: 'Alice Johnson',
})
}
>
<Text>Go to User Detail</Text>
</Pressable>
);
}
// Read params in the target screen
function UserDetailScreen() {
const route = useRoute();
const { userId, userName } = route.params as { userId: string; userName: string };
return <Text>Viewing profile: {userName} ({userId})</Text>;
}Tab and Drawer Navigators
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createDrawerNavigator } from '@react-navigation/drawer';
import Ionicons from '@expo/vector-icons/Ionicons';
const Tab = createBottomTabNavigator();
function MainTabs() {
return (
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
const iconName =
route.name === 'Feed' ? 'home' :
route.name === 'Search' ? 'search' :
route.name === 'Profile' ? 'person' : 'ellipsis-horizontal';
return <Ionicons name={iconName as any} size={size} color={color} />;
},
tabBarActiveTintColor: '#3b82f6',
tabBarInactiveTintColor: '#9ca3af',
tabBarStyle: { paddingBottom: 4 },
headerShown: false,
})}
>
<Tab.Screen name="Feed" component={FeedScreen} />
<Tab.Screen name="Search" component={SearchScreen} />
<Tab.Screen name="Profile" component={ProfileScreen} />
</Tab.Navigator>
);
}
// Drawer navigator
const Drawer = createDrawerNavigator();
function RootDrawer() {
return (
<Drawer.Navigator
screenOptions={{
drawerActiveTintColor: '#3b82f6',
drawerStyle: { backgroundColor: '#f9fafb' },
}}
>
<Drawer.Screen name="Home" component={MainTabs} />
<Drawer.Screen name="Settings" component={SettingsScreen} />
<Drawer.Screen name="Help" component={HelpScreen} />
</Drawer.Navigator>
);
}5. State Management
React Native uses the same state management patterns as React web applications. Choose the right tool based on your app's complexity.
useState and useReducer (Local State)
import React, { useState, useReducer } from 'react';
import { View, TextInput, Pressable, Text } from 'react-native';
// useState — simple values
function SearchBar() {
const [query, setQuery] = useState('');
const [isLoading, setIsLoading] = useState(false);
const handleSearch = async () => {
setIsLoading(true);
await performSearch(query);
setIsLoading(false);
};
return (
<View style={{ flexDirection: 'row', gap: 8 }}>
<TextInput
value={query}
onChangeText={setQuery}
placeholder="Search..."
style={{ flex: 1, borderWidth: 1, borderRadius: 8, padding: 10 }}
returnKeyType="search"
onSubmitEditing={handleSearch}
/>
<Pressable onPress={handleSearch} style={{ padding: 10, backgroundColor: '#3b82f6', borderRadius: 8 }}>
<Text style={{ color: '#fff' }}>{isLoading ? '...' : 'Go'}</Text>
</Pressable>
</View>
);
}
// useReducer — complex state machines
type CartState = { items: CartItem[]; total: number };
type CartAction =
| { type: 'ADD_ITEM'; payload: CartItem }
| { type: 'REMOVE_ITEM'; payload: string }
| { type: 'CLEAR' };
function cartReducer(state: CartState, action: CartAction): CartState {
switch (action.type) {
case 'ADD_ITEM':
return {
items: [...state.items, action.payload],
total: state.total + action.payload.price,
};
case 'REMOVE_ITEM':
const removed = state.items.find(i => i.id === action.payload);
return {
items: state.items.filter(i => i.id !== action.payload),
total: state.total - (removed?.price ?? 0),
};
case 'CLEAR':
return { items: [], total: 0 };
default:
return state;
}
}
function Cart() {
const [cart, dispatch] = useReducer(cartReducer, { items: [], total: 0 });
return (
<View>
<Text>Total: ${cart.total.toFixed(2)}</Text>
<Pressable onPress={() => dispatch({ type: 'CLEAR' })}>
<Text>Clear Cart</Text>
</Pressable>
</View>
);
}Zustand for Global State (Recommended)
Zustand is a lightweight, hooks-based state manager that works exceptionally well with React Native. It has zero boilerplate, supports persistence with AsyncStorage, and does not require a Provider wrapper.
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
// Define store interface
interface AuthStore {
user: { id: string; name: string; token: string } | null;
isAuthenticated: boolean;
login: (user: { id: string; name: string; token: string }) => void;
logout: () => void;
}
// Create persisted store — survives app restarts
const useAuthStore = create<AuthStore>()(
persist(
(set) => ({
user: null,
isAuthenticated: false,
login: (user) => set({ user, isAuthenticated: true }),
logout: () => set({ user: null, isAuthenticated: false }),
}),
{
name: 'auth-storage',
storage: createJSONStorage(() => AsyncStorage),
}
)
);
// Use in any component — no Provider needed
function ProfileScreen() {
const { user, logout, isAuthenticated } = useAuthStore();
if (!isAuthenticated) return <LoginScreen />;
return (
<View style={{ padding: 20 }}>
<Text style={{ fontSize: 20 }}>Welcome, {user?.name}</Text>
<Pressable onPress={logout} style={{ marginTop: 20, padding: 12, backgroundColor: '#ef4444', borderRadius: 8 }}>
<Text style={{ color: '#fff', textAlign: 'center' }}>Sign Out</Text>
</Pressable>
</View>
);
}6. Native APIs
React Native and Expo provide access to device hardware and OS capabilities. Here are the most commonly used native APIs.
Camera
import { CameraView, useCameraPermissions } from 'expo-camera';
import { useState } from 'react';
import { Button, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
export default function CameraScreen() {
const [facing, setFacing] = useState<'back' | 'front'>('back');
const [permission, requestPermission] = useCameraPermissions();
if (!permission) return <View />;
if (!permission.granted) {
return (
<View style={styles.container}>
<Text style={styles.message}>Camera access is required to take photos</Text>
<Button onPress={requestPermission} title="Grant Permission" />
</View>
);
}
return (
<View style={styles.container}>
<CameraView style={styles.camera} facing={facing}>
<View style={styles.buttonContainer}>
<TouchableOpacity onPress={() => setFacing(f => f === 'back' ? 'front' : 'back')}>
<Text style={styles.text}>Flip Camera</Text>
</TouchableOpacity>
</View>
</CameraView>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center' },
camera: { flex: 1 },
buttonContainer: { flex: 1, flexDirection: 'row', backgroundColor: 'transparent', margin: 64 },
message: { textAlign: 'center', paddingBottom: 10 },
text: { fontSize: 24, fontWeight: 'bold', color: 'white' },
});Location Services
import * as Location from 'expo-location';
import { useEffect, useState } from 'react';
type Coords = { latitude: number; longitude: number };
export function useCurrentLocation() {
const [location, setLocation] = useState<Coords | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
(async () => {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
setError('Location permission denied');
return;
}
const loc = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.Balanced,
});
setLocation({
latitude: loc.coords.latitude,
longitude: loc.coords.longitude,
});
})();
}, []);
return { location, error };
}
// Watch location changes in real time
export function useLocationTracking() {
const [locations, setLocations] = useState<Coords[]>([]);
useEffect(() => {
let sub: Location.LocationSubscription;
(async () => {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') return;
sub = await Location.watchPositionAsync(
{ accuracy: Location.Accuracy.High, timeInterval: 5000, distanceInterval: 10 },
(loc) => setLocations(prev => [...prev, { latitude: loc.coords.latitude, longitude: loc.coords.longitude }])
);
})();
return () => sub?.remove();
}, []);
return locations;
}Push Notifications
import * as Notifications from 'expo-notifications';
import { useEffect, useRef } from 'react';
// Configure notification handler
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: false,
}),
});
export async function registerForPushNotifications(): Promise<string | null> {
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') return null;
const token = await Notifications.getExpoPushTokenAsync({
projectId: 'your-project-id', // From app.json extra.eas.projectId
});
return token.data;
}
// Send a local notification (useful for reminders)
export async function scheduleLocalNotification(
title: string,
body: string,
trigger: Notifications.NotificationTriggerInput
) {
return Notifications.scheduleNotificationAsync({
content: { title, body, sound: 'default' },
trigger,
});
}
// Listen to notification events
export function useNotificationListener(onReceive: (n: Notifications.Notification) => void) {
const listenerRef = useRef<Notifications.Subscription>();
useEffect(() => {
listenerRef.current = Notifications.addNotificationReceivedListener(onReceive);
return () => listenerRef.current?.remove();
}, [onReceive]);
}AsyncStorage and SecureStore
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as SecureStore from 'expo-secure-store';
// AsyncStorage — unencrypted key-value storage (good for preferences/cache)
const storeData = async (key: string, value: unknown) => {
await AsyncStorage.setItem(key, JSON.stringify(value));
};
const getData = async <T>(key: string): Promise<T | null> => {
const item = await AsyncStorage.getItem(key);
return item ? JSON.parse(item) : null;
};
const removeData = async (key: string) => {
await AsyncStorage.removeItem(key);
};
// SecureStore — encrypted storage backed by Keychain (iOS) / Keystore (Android)
// Use this for tokens, passwords, sensitive user data
const storeToken = async (token: string) => {
await SecureStore.setItemAsync('auth_token', token);
};
const getToken = async (): Promise<string | null> => {
return await SecureStore.getItemAsync('auth_token');
};
const deleteToken = async () => {
await SecureStore.deleteItemAsync('auth_token');
};7. Styling in React Native
React Native does not use CSS. Instead, styles are JavaScript objects created with StyleSheet.create(). The styling system is inspired by CSS but uses camelCase property names, and all dimensions are in density-independent pixels (dp) by default.
StyleSheet API
import { StyleSheet, Platform, Dimensions } from 'react-native';
const { width, height } = Dimensions.get('window');
const styles = StyleSheet.create({
// Flexbox is the default layout system
container: {
flex: 1,
flexDirection: 'column', // 'row' | 'column' (default is column, unlike CSS!)
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: '#f9fafb',
},
// Text styles
heading: {
fontSize: 24,
fontWeight: '700', // '100' - '900' or 'bold' | 'normal'
letterSpacing: 0.5,
lineHeight: 32,
color: '#111827',
},
// Borders
card: {
borderRadius: 12,
borderWidth: 1,
borderColor: '#e5e7eb',
backgroundColor: '#ffffff',
padding: 16,
// Shorthand: no 'border: 1px solid ...' in RN
// Shadow (iOS)
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.08,
shadowRadius: 8,
// Shadow (Android)
elevation: 4,
},
// Responsive with Dimensions
image: {
width: width - 32, // Full width minus margins
height: (width - 32) * 0.5625, // 16:9 ratio
borderRadius: 8,
},
// Platform-specific styles
platformText: {
...Platform.select({
ios: { fontFamily: 'San Francisco', fontSize: 15 },
android: { fontFamily: 'Roboto', fontSize: 15 },
default: { fontSize: 15 },
}),
},
});Platform-Specific Code
import { Platform } from 'react-native';
// Inline platform check
const shadowStyle = Platform.OS === 'ios'
? { shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4 }
: { elevation: 4 };
// Platform.select
const statusBarHeight = Platform.select({ ios: 44, android: 0, default: 0 });
// Platform-specific files (automatic selection by Metro bundler)
// Button.ios.tsx — loaded on iOS
// Button.android.tsx — loaded on Android
// Button.tsx — fallback for both
// Safe area handling
import { SafeAreaView, SafeAreaProvider } from 'react-native-safe-area-context';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
function ScreenWithSafeArea() {
const insets = useSafeAreaInsets();
return (
<View style={{ flex: 1, paddingTop: insets.top, paddingBottom: insets.bottom }}>
{/* Content is never hidden behind notch or home indicator */}
</View>
);
}8. Performance Optimization
React Native performance hinges on minimizing JavaScript thread work and re-renders, optimizing list rendering, and leveraging the native thread for animations.
React.memo and useMemo
import React, { memo, useMemo, useCallback } from 'react';
// React.memo — prevent re-render if props haven't changed
const ProductCard = memo(function ProductCard({ id, name, price, onPress }: ProductCardProps) {
return (
<Pressable onPress={() => onPress(id)}>
<Text>{name}</Text>
<Text>${price.toFixed(2)}</Text>
</Pressable>
);
});
// useMemo — expensive computation
function ProductList({ products, minPrice }: ProductListProps) {
// Only re-computes when products or minPrice changes
const filteredProducts = useMemo(
() => products.filter(p => p.price >= minPrice).sort((a, b) => a.price - b.price),
[products, minPrice]
);
// useCallback — stable reference for callbacks passed to memoized components
const handlePress = useCallback((id: string) => {
navigation.navigate('ProductDetail', { productId: id });
}, [navigation]);
return (
<FlatList
data={filteredProducts}
renderItem={({ item }) => <ProductCard {...item} onPress={handlePress} />}
keyExtractor={item => item.id}
/>
);
}Hermes JavaScript Engine
Hermes is a JavaScript engine optimized for React Native, open-sourced by Meta. It uses ahead-of-time (AOT) bytecode compilation, which significantly reduces app startup time (sometimes by 50%), lowers memory consumption, and improves battery usage. Hermes is the default engine in React Native 0.70+ and all new Expo projects.
// android/app/build.gradle — Hermes enabled by default
android {
defaultConfig {
// Hermes is enabled via build.gradle in RN 0.70+
}
}
// Check if Hermes is running at runtime
const isHermes = () => !!global.HermesInternal;
console.log('Hermes engine:', isHermes()); // true in production builds
// iOS: enabled in Podfile (default true for new projects)
# In ios/Podfile:
# :hermes_enabled => true
// Verify Hermes in Metro logs:
// "Hermes is enabled" appears in the Metro bundler outputAnimation with Reanimated 3
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming,
runOnJS,
} from 'react-native-reanimated';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
// Reanimated runs animations on the UI thread — no bridge involved
function AnimatedCard() {
const scale = useSharedValue(1);
const opacity = useSharedValue(1);
const tap = Gesture.Tap()
.onBegin(() => {
scale.value = withSpring(0.95);
opacity.value = withTiming(0.8, { duration: 150 });
})
.onFinalize(() => {
scale.value = withSpring(1);
opacity.value = withTiming(1, { duration: 150 });
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
opacity: opacity.value,
}));
return (
<GestureDetector gesture={tap}>
<Animated.View style={[styles.card, animatedStyle]}>
<Text>Tap me!</Text>
</Animated.View>
</GestureDetector>
);
}Debugging with Flipper
# Flipper — desktop debugging tool for React Native
# Available at https://fbflipper.com/
# Features:
# - Network Inspector: view all HTTP/WebSocket requests
# - React DevTools: inspect component tree and props
# - Layout Inspector: visualize the view hierarchy
# - Performance Monitor: JS/UI frame rate graphs
# - Hermes Debugger: breakpoints, profiling, memory snapshots
# - Redux DevTools via flipper-plugin-redux-debugger
# Install Flipper debug client in your app (dev builds only):
# iOS: automatically connected when running in debug mode
# Android: enable USB debugging and connect via ADB
# In development, shake the device or press Cmd+D (iOS Simulator)
# to open the developer menu with:
# - Reload JS
# - Performance Monitor
# - Element Inspector
# - Toggle Remote JS Debugging
# For Expo:
npx expo start --dev-client # Full Flipper support with dev client9. React Native vs Flutter vs Ionic vs Expo
Choosing a cross-platform framework is one of the most important architectural decisions. Here is a comprehensive comparison of the leading options in 2026.
| Feature | React Native | Flutter | Ionic | Expo |
|---|---|---|---|---|
| Language | JavaScript / TypeScript | Dart | HTML / CSS / JS | JavaScript / TypeScript |
| Rendering | Native components (JSI + Fabric) | Custom renderer (Skia) | WebView (Capacitor) | Native via React Native |
| Performance | Excellent (New Arch) | Excellent | Good (WebView limited) | Excellent |
| Code Sharing | iOS + Android + Web (RNW) | iOS + Android + Web + Desktop | iOS + Android + Web | iOS + Android + Web (EAS) |
| Learning Curve | Low (if you know React) | Medium (new language) | Low (web skills) | Very Low |
| Ecosystem | Very large (npm) | Growing (pub.dev) | Large (npm) | Large (Expo SDK) |
| Hot Reload | Fast Refresh | Hot Reload + Hot Restart | Live Reload | Fast Refresh + OTA |
| Native Modules | Full (Swift / Kotlin) | Full (Platform Channels) | Via Capacitor plugins | Expo modules or bare |
| OTA Updates | Via CodePush | Limited | Via Appflow | EAS Update (built-in) |
| Best For | React devs, complex apps | Pixel-perfect custom UI | Web devs, simple apps | Rapid development, prototypes |
| Backed By | Meta (open source) | Google (open source) | Ionic / Capacitor team | Expo (Shopify-backed) |
Quick Decision Guide
- Already know React? Use React Native or Expo — reuse your skills and npm ecosystem
- Need maximum visual customization? Flutter renders every pixel with Skia, giving pixel-perfect control across platforms
- Building a simple app quickly? Expo managed workflow is the fastest path to a working iOS + Android app
- Web developer with no native experience? Ionic/Capacitor lets you use familiar HTML/CSS/JS with web frameworks like React, Vue, or Angular
- Enterprise app needing native features? React Native bare workflow with EAS Build provides the best balance of control and convenience
10. Frequently Asked Questions
What is React Native New Architecture and should I use it in 2026?
The New Architecture replaces the old asynchronous JavaScript bridge with JSI (JavaScript Interface) — a C++ layer enabling direct, synchronous calls between JavaScript and native code. The new rendering system Fabric runs React's reconciler on native threads, enabling concurrent rendering and eliminating frame drops during animations. TurboModules load native modules lazily, reducing startup time. It is stable in React Native 0.74+ and enabled by default in all new projects. Existing projects should migrate — the performance improvements are significant, especially for animation-heavy apps.
Should I use Expo or bare React Native?
Use Expo managed workflow for most projects. It handles certificates, build servers, over-the-air updates, and provides 50+ pre-built native modules. EAS Build lets you build iOS apps from any OS — you do not need a Mac. Choose bare React Native (or Expo bare) when you need to write custom native code in Swift or Kotlin, integrate proprietary SDKs, or have complex native build requirements.
Can I use TypeScript with React Native?
Yes — TypeScript is fully supported and is the recommended default. New projects created with npx create-expo-app --template blank-typescript or npx react-native init MyApp include TypeScript configuration out of the box. You get full type safety for component props, navigation params (with typed route definitions), StyleSheet definitions, and API response shapes.
How do I debug React Native apps?
Use Flipper for network inspection, component tree debugging, and performance profiling. React DevTools works for inspecting component state and props. For native crashes, use Xcode Instruments for iOS and Android Studio's profiler for Android. Shake the device (or press Cmd+D on iOS simulator / Cmd+M on Android emulator) to access the developer menu with reload, element inspector, and performance monitor.
How do I navigate between screens?
Install React Navigation (@react-navigation/native) plus the navigator packages you need. Wrap your app in NavigationContainer. Use createNativeStackNavigator for stack navigation, createBottomTabNavigator for tabs. Access the navigation object via the useNavigation hook and read route params via useRoute. For Expo projects, consider Expo Router (SDK 50+) for file-based routing similar to Next.js.
What is Expo Router and how does it differ from React Navigation?
Expo Router is a file-based routing system built on top of React Navigation. Files in the app/ directory automatically become routes — no manual navigator setup required. It supports deep linking automatically, shared layouts (similar to Next.js layouts), and typed routes. React Navigation gives more programmatic control and is better for complex custom navigation patterns. Expo Router is recommended for new Expo SDK 50+ projects.
How do I handle global state in React Native?
For small apps, React Context with useReducer is sufficient. For medium to large apps, Zustand is the recommended choice — it is lightweight, requires no Provider boilerplate, supports AsyncStorage persistence, and works perfectly with React Native. Redux Toolkit remains popular in large enterprise teams. Avoid using Context for high-frequency updates (like real-time data) as it re-renders all consumers.
How do I deploy a React Native app to the App Store and Google Play?
With Expo, use EAS Build (eas build --platform ios and eas build --platform android) to create production binaries in Expo's cloud, then EAS Submit to upload directly to the App Store and Google Play. For bare React Native, build with Xcode (iOS) or Gradle (./gradlew bundleRelease for Android), then submit through App Store Connect and Google Play Console. You need an Apple Developer account ($99/year) and a Google Play Developer account ($25 one-time fee).
Conclusion
React Native in 2026 is more capable than ever. The New Architecture removes the main performance bottlenecks of the old bridge, Expo's EAS platform streamlines the build and deployment workflow, and the ecosystem — including React Navigation, Zustand, Reanimated, and Expo's SDK — is mature and well-maintained.
For most developers coming from a React web background, Expo with the managed workflow is the fastest path to a production-quality iOS and Android app. Start with npx create-expo-app --template blank-typescript, add React Navigation for routing, Zustand for state management, and EAS Build when you are ready to ship.
Master the core components (View, Text, FlatList, Pressable), understand React Native's flexbox-first layout system, apply the StyleSheet API for optimized styles, and profile your app with Flipper and Hermes. With these fundamentals in place, you have everything you need to build performant, polished cross-platform mobile applications.