About us Guides Projects Contacts
Админка
please wait

Introduction

React Native enables building native mobile applications using JavaScript and React. Unlike hybrid frameworks, React Native renders actual native components, providing performance close to native development while sharing most code between iOS and Android. This guide covers practical React Native development with Expo, the recommended toolchain for most projects.

Project Setup

Create Project with Expo

# Install the Expo CLI globally
npm install -g expo-cli
# Create a new project
npx create-expo-app@latest MyApp
cd MyApp
# Start the development server
npx expo start

Project Structure

MyApp/
├── app/ # Expo Router screens
│ ├── (tabs)/ # Tab navigation
│ │ ├── index.tsx # Home tab
│ │ ├── profile.tsx # Profile tab
│ │ └── _layout.tsx # Tab layout
│ ├── _layout.tsx # Root layout
│ └── modal.tsx # Modal screen
├── components/ # Reusable components
├── constants/ # App constants
├── hooks/ # Custom hooks
├── services/ # API services
├── assets/ # Images, fonts
├── app.json # Expo config
└── package.json

Core Components

Views and Layout

import { View, Text, StyleSheet } from 'react-native';
export default function HomeScreen() {
return (
<View style={styles.container}>
<Text style={styles.title}>Welcome</Text>
<View style={styles.card}>
<Text>Card content</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
backgroundColor: '#fff',
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 16,
},
card: {
backgroundColor: '#f5f5f5',
padding: 16,
borderRadius: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3, // Android shadow
},
});

Flexbox Layout

<View style={styles.row}>
<View style={styles.box} />
<View style={styles.box} />
<View style={styles.box} />
</View>
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
justifyContent: 'space-between', // Main axis
alignItems: 'center', // Cross axis
gap: 12,
},
box: {
flex: 1, // Equal width
height: 100,
backgroundColor: '#007AFF',
},
});

Lists

import { FlatList, View, Text } from 'react-native';
interface Item {
id: string;
title: string;
}
export default function ItemList() {
const items: Item[] = [
{ id: '1', title: 'First Item' },
{ id: '2', title: 'Second Item' },
];
const renderItem = ({ item }: { item: Item }) => (
<View style={styles.item}>
<Text>{item.title}</Text>
</View>
);
return (
<FlatList
data={items}
renderItem={renderItem}
keyExtractor={(item) => item.id}
ItemSeparatorComponent={() => <View style={styles.separator} />}
ListEmptyComponent={<Text>No items</Text>}
refreshing={false}
onRefresh={() => {/*Refresh logic*/}}
/>
);
}

User Input

import { useState } from 'react';
import {
TextInput,
TouchableOpacity,
Text,
View
} from 'react-native';
export default function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = () => {
console.log('Login:', email, password);
};
return (
<View style={styles.container}>
<TextInput
style={styles.input}
placeholder="Email"
value={email}
onChangeText={setEmail}
keyboardType="email-address"
autoCapitalize="none"
autoComplete="email"
/>
<TextInput
style={styles.input}
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
autoComplete="password"
/>
<TouchableOpacity style={styles.button} onPress={handleSubmit}>
<Text style={styles.buttonText}>Login</Text>
</TouchableOpacity>
</View>
);
}

Navigation with Expo Router

Tab Navigation

// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
export default function TabLayout() {
return (
<Tabs screenOptions={{
tabBarActiveTintColor: '#007AFF',
}}>
<Tabs.Screen
name="index"
options={{
title: 'Home',
tabBarIcon: ({ color, size }) => (
<Ionicons name="home" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="profile"
options={{
title: 'Profile',
tabBarIcon: ({ color, size }) => (
<Ionicons name="person" size={size} color={color} />
),
}}
/>
</Tabs>
);
}

Stack Navigation

// app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen
name="details/[id]"
options={{ title: 'Details' }}
/>
<Stack.Screen
name="modal"
options={{ presentation: 'modal' }}
/>
</Stack>
);
}

Navigation and Params

// Navigate with params
import { Link, router } from 'expo-router';
// Declarative
<Link href="/details/123">View Details</Link>
// Imperative
router.push('/details/123');
router.replace('/home');
router.back();
// Access params
// app/details/[id].tsx
import { useLocalSearchParams } from 'expo-router';
export default function DetailsScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
return <Text>Item ID: {id}</Text>;
}

State Management

Context for Global State

// context/AuthContext.tsx
import { createContext, useContext, useState, ReactNode } from 'react';
interface User {
id: string;
email: string;
}
interface AuthContextType {
user: User | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const login = async (email: string, password: string) => {
// API call
const response = await api.login(email, password);
setUser(response.user);
};
const logout = () => {
setUser(null);
};
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}

API Integration

Fetch with Custom Hook

// hooks/useFetch.ts
import { useState, useEffect } from 'react';
interface UseFetchResult<T> {
data: T | null;
loading: boolean;
error: Error | null;
refetch: () => void;
}
export function useFetch<T>(url: string): UseFetchResult<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const fetchData = async () => {
setLoading(true);
try {
const response = await fetch(url);
if (!response.ok) throw new Error('Network error');
const json = await response.json();
setData(json);
} catch (e) {
setError(e as Error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, [url]);
return { data, loading, error, refetch: fetchData };
}
// Usage
function UserProfile({ userId }: { userId: string }) {
const { data: user, loading, error } = useFetch<User>(
`https://api.example.com/users/${userId}`
);
if (loading) return <ActivityIndicator />;
if (error) return <Text>Error: {error.message}</Text>;
if (!user) return <Text>User not found</Text>;
return <Text>{user.name}</Text>;
}

Device Features

Camera Access

import { Camera, CameraView } from 'expo-camera';
import { useState } from 'react';
export default function CameraScreen() {
const [permission, requestPermission] = Camera.useCameraPermissions();
if (!permission) {
return <View />;
}
if (!permission.granted) {
return (
<View style={styles.container}>
<Text>We need camera permission</Text>
<Button onPress={requestPermission} title="Grant Permission" />
</View>
);
}
return (
<View style={styles.container}>
<CameraView style={styles.camera} facing="back">
{/*Camera overlay*/}
</CameraView>
</View>
);
}

Local Storage

import AsyncStorage from '@react-native-async-storage/async-storage';
// Store data
await AsyncStorage.setItem('userToken', 'abc123');
// Retrieve data
const token = await AsyncStorage.getItem('userToken');
// Remove data
await AsyncStorage.removeItem('userToken');
// Store object
await AsyncStorage.setItem('user', JSON.stringify(user));
const user = JSON.parse(await AsyncStorage.getItem('user') || '{}');

Push Notifications

import * as Notifications from 'expo-notifications';
import { useEffect, useRef, useState } from 'react';
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
}),
});
export function usePushNotifications() {
const [expoPushToken, setExpoPushToken] = useState<string>('');
const notificationListener = useRef<Notifications.Subscription>();
useEffect(() => {
registerForPushNotificationsAsync().then(token => {
if (token) setExpoPushToken(token);
});
notificationListener.current = Notifications.addNotificationReceivedListener(
notification => {
console.log('Notification:', notification);
}
);
return () => {
if (notificationListener.current) {
Notifications.removeNotificationSubscription(notificationListener.current);
}
};
}, []);
return expoPushToken;
}
async function registerForPushNotificationsAsync() {
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();
return token.data;
}

Building and Publishing

Build for Production

# Install the EAS CLI
npm install -g eas-cli
# Configure the project
eas build:configure
# Build for iOS
eas build --platform ios
# Build for Android
eas build --platform android
# Build for both
eas build --platform all

App Store Configuration

// app.json
{
"expo": {
"name": "MyApp",
"slug": "myapp",
"version": "1.0.0",
"ios": {
"bundleIdentifier": "com.company.myapp",
"buildNumber": "1"
},
"android": {
"package": "com.company.myapp",
"versionCode": 1
}
}
}

Over-the-Air Updates

# Publish update
eas update --branch production --message "Bug fix"
# Configure updates in app.json
{
"expo": {
"updates": {
"url": "https://u.expo.dev/your-project-id"
},
"runtimeVersion": {
"policy": "sdkVersion"
}
}
}

Performance Tips

  1. Use FlatList for long lists, not ScrollView.
  2. Memoize expensive computations with useMemo.
  3. Optimize images—use proper sizes and formats.
  4. Avoid inline styles—use StyleSheet.create.
  5. Use React.memo for pure components.
  6. Profile with React DevTools to find re-renders.

Conclusion

React Native with Expo provides a productive environment for mobile development. Use Expo Router for navigation, manage state with Context or Zustand, and leverage Expo's rich library of device APIs. The ability to share code between platforms while maintaining native performance makes React Native an excellent choice for cross-platform mobile development.

 
 
 
Языки
Темы
Copyright © 1999 — 2026
ZK Interactive