React Native Rehberi (React Native Guide)
📌 Ne Zaman Kullanılır?
✅ Kullan: JavaScript/React bilen ekip, hızlı cross-platform MVP, kod paylaşımı (web+mobil) ⚠️ Dikkat: Yoğun native özellik gerekiyorsa bridge overhead olabilir ❌ Kullanma: Oyun, AR/VR, çok yoğun animasyon (native veya Flutter daha iyi) Önerilen stack: React Native + TypeScript + Expo + React Navigation + Zustand
React Native ile iOS ve Android için tek kod tabanından mobil uygulama geliştirme rehberi. (A guide to building mobile apps for iOS and Android from a single codebase with React Native.)
1) React Native Nedir? (What is React Native?)
React Native, Facebook tarafından geliştirilen, JavaScript ve React kullanarak native mobil uygulamalar oluşturmayı sağlayan açık kaynak bir framework'tür. (React Native is an open-source framework developed by Facebook that allows building native mobile applications using JavaScript and React.)
Temel özellikler (Key features):
- Cross-platform — Tek kod tabanıyla iOS ve Android uygulaması (Single codebase for iOS and Android)
- Native bileşenler — WebView değil, gerçek native UI bileşenleri render eder (Renders real native UI components, not WebView)
- Hot Reload — Kod değişiklikleri anında yansır, hızlı geliştirme döngüsü (Instant code reflection, fast development cycle)
- JavaScript ekosistemi — npm/yarn paketleri, React bilgisi transfer edilir (npm/yarn packages, React knowledge transfers)
- Hermes engine — Meta'nın özel JS motoru, daha hızlı başlatma ve düşük bellek kullanımı (Meta's custom JS engine, faster startup and lower memory)
React Native vs Alternatifler (vs Alternatives)
| Özellik (Feature) | React Native | Flutter | Native (Swift/Kotlin) |
|---|---|---|---|
| Dil (Language) | JavaScript/TypeScript | Dart | Swift / Kotlin |
| UI Render | Native bileşenler | Kendi render motoru (Skia) | Native |
| Performans | İyi (Good) | Çok iyi (Very good) | En iyi (Best) |
| Öğrenme eğrisi (Learning curve) | Düşük (React bilenler için) | Orta | Yüksek (2 platform) |
| Kod paylaşımı (Code sharing) | ~90-95% | ~95% | 0% |
| Ekosistem | Çok geniş (Very large) | Büyüyen (Growing) | Platform-specific |
| Hot Reload | ✅ | ✅ | Sınırlı (Limited) |
| Web desteği | React Native Web | Flutter Web | ❌ |
Expo vs Bare Workflow Karşılaştırma (Expo vs Bare Workflow Comparison)
| Özellik (Feature) | Expo (Managed) | Expo (Dev Build) | Bare Workflow |
|---|---|---|---|
| Kurulum zorluğu (Setup difficulty) | Çok kolay | Kolay | Orta-Zor |
| Native modül erişimi | Sınırlı (Expo SDK) | Tam (Full) | Tam (Full) |
| OTA güncellemeler (OTA updates) | ✅ EAS Update | ✅ EAS Update | Manuel (expo-updates) |
| Build süreci | EAS Build (bulut) | EAS Build / Lokal | Lokal (Xcode/Android Studio) |
| Xcode/Android Studio gerekli mi? | ❌ (EAS ile) | ✅ (lokal build) | ✅ |
| Ejecting | Artık gerekli değil | — | — |
| Custom native kod | ❌ | ✅ Config plugins | ✅ Direkt erişim |
| Tavsiye edilen durum | Prototip, MVP, basit uygulama | Üretim uygulamaları | Çok özel native ihtiyaçlar |
Tavsiye (Recommendation): Expo (Dev Build) ile başlayın. %95+ kullanım senaryosunu karşılar. (Start with Expo Dev Build. It covers 95%+ of use cases.)
2) Kurulum (Installation)
Expo CLI ile Kurulum (Setup with Expo CLI)
# Node.js 18+ gerekli (Node.js 18+ required)
# npx ile Expo projesi oluştur (Create an Expo project with npx)
npx create-expo-app@latest my-app --template blank-typescript
cd my-app
# Geliştirme sunucusunu başlat (Start development server)
npx expo start
# Expo Go uygulamasını telefonuna indir ve QR kodu tara
# (Download Expo Go app on your phone and scan QR code)Expo Dev Build (Üretim için Önerilen — Recommended for Production)
# EAS CLI kur (Install EAS CLI)
npm install -g eas-cli
# EAS ile giriş yap (Login with EAS)
eas login
# Dev build yapılandır (Configure dev build)
eas build:configure
# iOS dev build (simulator)
eas build --profile development --platform ios
# Android dev build (emulator)
eas build --profile development --platform android
# Dev client ile başlat (Start with dev client)
npx expo start --dev-clientReact Native CLI ile Kurulum (Setup with React Native CLI)
# React Native CLI projesi oluştur (Create React Native CLI project)
npx @react-native-community/cli init MyApp --template react-native-template-typescript
cd MyApp
# iOS bağımlılıkları (iOS dependencies — macOS only)
cd ios && pod install && cd ..
# Uygulamayı çalıştır (Run the app)
npx react-native run-android # Android
npx react-native run-ios # iOS (sadece macOS)Android Studio Kurulumu (Android Studio Setup)
# 1. Android Studio indir: https://developer.android.com/studio
# 2. SDK Manager'dan kur (Install from SDK Manager):
# - Android SDK Platform 34
# - Android SDK Build-Tools 34
# - Android Emulator
# - Android SDK Platform-Tools
# 3. Ortam değişkenleri (~/.bashrc veya ~/.zshrc)
export ANDROID_HOME=$HOME/Android/Sdk
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/platform-tools
export PATH=$PATH:$ANDROID_HOME/tools
export PATH=$PATH:$ANDROID_HOME/tools/bin
# 4. Emulator oluştur (Create emulator)
# Android Studio → Virtual Device Manager → Create Device
# Pixel 7 → System Image: API 34 → Finish
# 5. Emulator başlat (Start emulator)
emulator -avd Pixel_7_API_34Xcode Kurulumu (Xcode Setup — macOS Only)
# 1. App Store'dan Xcode indir (Download Xcode from App Store)
# 2. Command Line Tools kur (Install Command Line Tools)
xcode-select --install
# 3. CocoaPods kur (Install CocoaPods)
sudo gem install cocoapods
# 4. iOS Simulator açmak için (To open iOS Simulator):
# Xcode → Open Developer Tool → Simulator
# veya terminal:
open -a SimulatorTypeScript Yapılandırması (TypeScript Configuration)
// tsconfig.json
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@screens/*": ["src/screens/*"],
"@hooks/*": ["src/hooks/*"],
"@utils/*": ["src/utils/*"],
"@services/*": ["src/services/*"],
"@types/*": ["src/types/*"],
"@assets/*": ["assets/*"]
}
},
"include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
}3) Proje Yapısı (Project Structure)
Önerilen Yapı (Recommended Structure)
my-app/
├── app.json # Expo yapılandırması (Expo config)
├── App.tsx # Giriş noktası (Entry point)
├── tsconfig.json # TypeScript ayarları
├── babel.config.js # Babel yapılandırması
├── eas.json # EAS Build yapılandırması
├── assets/ # Statik dosyalar (Static files)
│ ├── images/
│ ├── fonts/
│ └── icons/
├── src/
│ ├── components/ # Yeniden kullanılabilir bileşenler (Reusable components)
│ │ ├── ui/ # Genel UI bileşenleri (General UI)
│ │ │ ├── Button.tsx
│ │ │ ├── Input.tsx
│ │ │ ├── Card.tsx
│ │ │ └── Loading.tsx
│ │ └── shared/ # Paylaşılan bileşenler (Shared)
│ │ ├── Header.tsx
│ │ └── Avatar.tsx
│ ├── screens/ # Ekranlar (Screens)
│ │ ├── auth/
│ │ │ ├── LoginScreen.tsx
│ │ │ └── RegisterScreen.tsx
│ │ ├── home/
│ │ │ └── HomeScreen.tsx
│ │ └── profile/
│ │ └── ProfileScreen.tsx
│ ├── navigation/ # Navigasyon yapılandırması (Navigation config)
│ │ ├── AppNavigator.tsx
│ │ ├── AuthNavigator.tsx
│ │ └── types.ts
│ ├── hooks/ # Custom hook'lar
│ │ ├── useAuth.ts
│ │ ├── useTheme.ts
│ │ └── useApi.ts
│ ├── services/ # API servisleri (API services)
│ │ ├── api.ts
│ │ ├── authService.ts
│ │ └── userService.ts
│ ├── store/ # State management
│ │ ├── useAuthStore.ts
│ │ └── useAppStore.ts
│ ├── utils/ # Yardımcı fonksiyonlar (Helper functions)
│ │ ├── formatters.ts
│ │ ├── validators.ts
│ │ └── constants.ts
│ ├── types/ # TypeScript tipleri (TypeScript types)
│ │ ├── api.ts
│ │ ├── navigation.ts
│ │ └── models.ts
│ └── theme/ # Tema yapılandırması (Theme config)
│ ├── colors.ts
│ ├── spacing.ts
│ └── typography.ts
└── __tests__/ # Testler (Tests)
├── components/
└── screens/app.json Yapılandırması (app.json Configuration)
{
"expo": {
"name": "MyApp",
"slug": "my-app",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "automatic",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"assetBundlePatterns": ["**/*"],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.yourcompany.myapp",
"buildNumber": "1"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "com.yourcompany.myapp",
"versionCode": 1
},
"plugins": [
"expo-router",
"expo-secure-store",
[
"expo-camera",
{ "cameraPermission": "Fotoğraf çekmek için kamera erişimi gerekli." }
]
]
}
}4) Temel Bileşenler (Core Components)
View — Temel Konteyner (Basic Container)
import { View, StyleSheet } from 'react-native';
// View = web'deki <div> karşılığı (View is the equivalent of <div> on web)
function CardContainer({ children }: { children: React.ReactNode }) {
return <View style={styles.card}>{children}</View>;
}
const styles = StyleSheet.create({
card: {
backgroundColor: '#fff',
borderRadius: 12,
padding: 16,
marginVertical: 8,
// Gölge (Shadow) — Platform-specific
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3, // Android gölge (Android shadow)
},
});Text — Metin Gösterimi (Text Display)
import { Text, StyleSheet } from 'react-native';
// ⚠️ React Native'de tüm metinler <Text> içinde olmalı
// (All text must be inside <Text> in React Native)
function Typography() {
return (
<View>
<Text style={styles.title}>Başlık (Title)</Text>
<Text style={styles.body}>
Normal metin. <Text style={styles.bold}>Kalın metin.</Text>
</Text>
<Text style={styles.body} numberOfLines={2} ellipsizeMode="tail">
Çok uzun metin burada kesilir... (Long text will be truncated here...)
</Text>
<Text style={styles.link} onPress={() => console.log('tıklandı')}>
Tıklanabilir metin (Clickable text)
</Text>
</View>
);
}
const styles = StyleSheet.create({
title: { fontSize: 24, fontWeight: 'bold', color: '#1a1a1a', marginBottom: 8 },
body: { fontSize: 16, color: '#333', lineHeight: 24 },
bold: { fontWeight: '700' },
link: { fontSize: 16, color: '#007AFF', textDecorationLine: 'underline' },
});ScrollView — Kaydırılabilir Alan (Scrollable Area)
import { ScrollView, RefreshControl } from 'react-native';
function ScrollableContent() {
const [refreshing, setRefreshing] = useState(false);
const onRefresh = async () => {
setRefreshing(true);
await fetchData();
setRefreshing(false);
};
return (
<ScrollView
style={{ flex: 1 }}
contentContainerStyle={{ padding: 16 }}
showsVerticalScrollIndicator={false}
keyboardShouldPersistTaps="handled" // Klavye açıkken dokunma (Touch while keyboard open)
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
>
{/* İçerik buraya (Content here) */}
</ScrollView>
);
}
// ⚠️ ScrollView tüm içeriği render eder — uzun listeler için FlatList kullan
// (ScrollView renders all content — use FlatList for long lists)FlatList — Performanslı Liste (Performant List)
import { FlatList, View, Text, StyleSheet } from 'react-native';
interface User {
id: string;
name: string;
email: string;
}
function UserList() {
const [users, setUsers] = useState<User[]>([]);
const [refreshing, setRefreshing] = useState(false);
const renderItem = ({ item, index }: { item: User; index: number }) => (
<View style={styles.userCard}>
<Text style={styles.userName}>{item.name}</Text>
<Text style={styles.userEmail}>{item.email}</Text>
</View>
);
const renderEmpty = () => (
<View style={styles.emptyContainer}>
<Text style={styles.emptyText}>Kullanıcı bulunamadı (No users found)</Text>
</View>
);
const renderSeparator = () => <View style={styles.separator} />;
const renderHeader = () => (
<Text style={styles.headerText}>Kullanıcılar (Users)</Text>
);
const renderFooter = () => (
<Text style={styles.footerText}>{users.length} kullanıcı (users)</Text>
);
return (
<FlatList
data={users}
renderItem={renderItem}
keyExtractor={(item) => item.id}
ListEmptyComponent={renderEmpty}
ItemSeparatorComponent={renderSeparator}
ListHeaderComponent={renderHeader}
ListFooterComponent={renderFooter}
refreshing={refreshing}
onRefresh={() => { /* yenileme mantığı (refresh logic) */ }}
onEndReached={() => { /* daha fazla yükle (load more) */ }}
onEndReachedThreshold={0.5}
showsVerticalScrollIndicator={false}
initialNumToRender={10}
maxToRenderPerBatch={5}
windowSize={5}
/>
);
}
const styles = StyleSheet.create({
userCard: { padding: 16, backgroundColor: '#fff' },
userName: { fontSize: 16, fontWeight: '600' },
userEmail: { fontSize: 14, color: '#666', marginTop: 4 },
separator: { height: 1, backgroundColor: '#e0e0e0' },
emptyContainer: { padding: 40, alignItems: 'center' },
emptyText: { fontSize: 16, color: '#999' },
headerText: { fontSize: 20, fontWeight: 'bold', padding: 16 },
footerText: { padding: 16, textAlign: 'center', color: '#999' },
});SectionList — Gruplu Liste (Grouped List)
import { SectionList } from 'react-native';
const DATA = [
{ title: 'Frontend', data: ['React', 'Vue', 'Angular'] },
{ title: 'Backend', data: ['Node.js', 'Laravel', 'Django'] },
{ title: 'Mobile', data: ['React Native', 'Flutter', 'Swift'] },
];
function GroupedList() {
return (
<SectionList
sections={DATA}
keyExtractor={(item, index) => item + index}
renderItem={({ item }) => <Text style={styles.item}>{item}</Text>}
renderSectionHeader={({ section: { title } }) => (
<Text style={styles.sectionHeader}>{title}</Text>
)}
/>
);
}TouchableOpacity & Pressable — Dokunmatik Alanlar (Touchable Areas)
import { TouchableOpacity, Pressable, StyleSheet } from 'react-native';
// TouchableOpacity — basit dokunma efekti (simple touch effect)
function SimpleButton({ title, onPress }: { title: string; onPress: () => void }) {
return (
<TouchableOpacity
style={styles.button}
onPress={onPress}
activeOpacity={0.7}
disabled={false}
>
<Text style={styles.buttonText}>{title}</Text>
</TouchableOpacity>
);
}
// Pressable — daha gelişmiş dokunma olayları (more advanced touch events)
function AdvancedButton({ title, onPress }: { title: string; onPress: () => void }) {
return (
<Pressable
style={({ pressed }) => [
styles.button,
pressed && styles.buttonPressed,
]}
onPress={onPress}
onLongPress={() => console.log('Uzun basıldı (Long pressed)')}
delayLongPress={500}
hitSlop={10} // Dokunma alanını genişlet (Expand touch area)
>
{({ pressed }) => (
<Text style={[styles.buttonText, pressed && { opacity: 0.7 }]}>
{title}
</Text>
)}
</Pressable>
);
}
const styles = StyleSheet.create({
button: {
backgroundColor: '#007AFF',
paddingHorizontal: 24,
paddingVertical: 12,
borderRadius: 8,
alignItems: 'center',
},
buttonPressed: { backgroundColor: '#0056b3' },
buttonText: { color: '#fff', fontSize: 16, fontWeight: '600' },
});Image — Görsel (Image)
import { Image, ImageBackground, StyleSheet } from 'react-native';
function ImageExamples() {
return (
<View>
{/* Yerel görsel (Local image) */}
<Image source={require('../assets/images/logo.png')} style={styles.logo} />
{/* Uzak görsel (Remote image) — boyut zorunlu (dimensions required) */}
<Image
source={{ uri: 'https://example.com/photo.jpg' }}
style={styles.remoteImage}
resizeMode="cover" // cover | contain | stretch | center
defaultSource={require('../assets/placeholder.png')} // iOS placeholder
onError={(e) => console.log('Görsel yüklenemedi (Image load failed)')}
/>
{/* Arka plan görseli (Background image) */}
<ImageBackground
source={require('../assets/bg.png')}
style={styles.backgroundImage}
imageStyle={{ borderRadius: 12 }}
>
<Text style={{ color: '#fff' }}>Arka plan üzerinde metin</Text>
</ImageBackground>
</View>
);
}
// Performanslı görsel kütüphanesi (Performant image library):
// expo-image veya react-native-fast-image önerilir
// (expo-image or react-native-fast-image recommended)
import { Image as ExpoImage } from 'expo-image';
function OptimizedImage() {
return (
<ExpoImage
source="https://example.com/photo.jpg"
style={{ width: 200, height: 200 }}
contentFit="cover"
placeholder={blurhash} // Blurhash placeholder
transition={300} // Geçiş animasyonu (Transition animation)
cachePolicy="memory-disk" // Önbellek politikası (Cache policy)
/>
);
}
const styles = StyleSheet.create({
logo: { width: 100, height: 100 },
remoteImage: { width: '100%', height: 200, borderRadius: 8 },
backgroundImage: { width: '100%', height: 200, justifyContent: 'center', alignItems: 'center' },
});TextInput — Metin Girişi (Text Input)
import { TextInput, StyleSheet } from 'react-native';
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const passwordRef = useRef<TextInput>(null);
return (
<View>
<TextInput
style={styles.input}
placeholder="E-posta (Email)"
placeholderTextColor="#999"
value={email}
onChangeText={setEmail}
keyboardType="email-address"
autoCapitalize="none"
autoCorrect={false}
returnKeyType="next"
onSubmitEditing={() => passwordRef.current?.focus()}
textContentType="emailAddress" // iOS otomatik doldurma (iOS autofill)
autoComplete="email" // Android otomatik doldurma
/>
<TextInput
ref={passwordRef}
style={styles.input}
placeholder="Şifre (Password)"
placeholderTextColor="#999"
value={password}
onChangeText={setPassword}
secureTextEntry // Şifre gizleme (Password hiding)
returnKeyType="done"
textContentType="password"
autoComplete="password"
/>
{/* Çok satırlı giriş (Multiline input) */}
<TextInput
style={[styles.input, styles.textArea]}
placeholder="Açıklama (Description)"
multiline
numberOfLines={4}
maxLength={500}
textAlignVertical="top" // Android için (For Android)
/>
</View>
);
}
const styles = StyleSheet.create({
input: {
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
padding: 12,
fontSize: 16,
marginBottom: 12,
backgroundColor: '#fff',
},
textArea: { height: 120 },
});Modal — Kalıcı Pencere (Modal Dialog)
import { Modal, View, Text, Pressable, StyleSheet } from 'react-native';
function ConfirmDialog({ visible, onClose, onConfirm, message }: {
visible: boolean;
onClose: () => void;
onConfirm: () => void;
message: string;
}) {
return (
<Modal
visible={visible}
transparent
animationType="fade" // none | slide | fade
onRequestClose={onClose} // Android geri tuşu (Android back button)
>
<View style={styles.overlay}>
<View style={styles.modalContent}>
<Text style={styles.modalMessage}>{message}</Text>
<View style={styles.modalActions}>
<Pressable style={styles.cancelBtn} onPress={onClose}>
<Text style={styles.cancelText}>İptal (Cancel)</Text>
</Pressable>
<Pressable style={styles.confirmBtn} onPress={onConfirm}>
<Text style={styles.confirmText}>Onayla (Confirm)</Text>
</Pressable>
</View>
</View>
</View>
</Modal>
);
}
const styles = StyleSheet.create({
overlay: {
flex: 1, backgroundColor: 'rgba(0,0,0,0.5)',
justifyContent: 'center', alignItems: 'center',
},
modalContent: {
backgroundColor: '#fff', borderRadius: 16,
padding: 24, width: '85%', maxWidth: 400,
},
modalMessage: { fontSize: 16, marginBottom: 24, textAlign: 'center' },
modalActions: { flexDirection: 'row', justifyContent: 'flex-end', gap: 12 },
cancelBtn: { paddingHorizontal: 20, paddingVertical: 10 },
confirmBtn: {
paddingHorizontal: 20, paddingVertical: 10,
backgroundColor: '#007AFF', borderRadius: 8,
},
cancelText: { color: '#666', fontSize: 16 },
confirmText: { color: '#fff', fontSize: 16, fontWeight: '600' },
});ActivityIndicator — Yükleme Göstergesi (Loading Indicator)
import { ActivityIndicator, View, StyleSheet } from 'react-native';
// Basit kullanım (Simple usage)
function LoadingSpinner() {
return <ActivityIndicator size="large" color="#007AFF" />;
}
// Tam ekran yükleme (Full screen loading)
function FullScreenLoader({ visible }: { visible: boolean }) {
if (!visible) return null;
return (
<View style={styles.loaderOverlay}>
<View style={styles.loaderBox}>
<ActivityIndicator size="large" color="#007AFF" />
<Text style={styles.loaderText}>Yükleniyor... (Loading...)</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
loaderOverlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0,0,0,0.3)',
justifyContent: 'center', alignItems: 'center', zIndex: 999,
},
loaderBox: {
backgroundColor: '#fff', borderRadius: 12, padding: 24, alignItems: 'center',
},
loaderText: { marginTop: 12, fontSize: 14, color: '#666' },
});5) Stil (Styling)
StyleSheet API
import { StyleSheet, View, Text } from 'react-native';
// ✅ StyleSheet.create kullan — performans optimizasyonu sağlar
// (Use StyleSheet.create — provides performance optimization)
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f5f5',
},
// Birden fazla stil birleştirme (Combining multiple styles)
// <View style={[styles.container, styles.centered]} />
centered: {
justifyContent: 'center',
alignItems: 'center',
},
});
// Dinamik stil (Dynamic style)
function DynamicStyle({ isActive }: { isActive: boolean }) {
return (
<View style={[
styles.container,
{ backgroundColor: isActive ? '#e0f0ff' : '#f5f5f5' },
]}>
<Text>Dinamik stil örneği (Dynamic style example)</Text>
</View>
);
}Flexbox — Web'den Farklar (Differences from Web)
// ⚠️ React Native Flexbox varsayılanları web'den farklı!
// (React Native Flexbox defaults differ from web!)
const flexExamples = StyleSheet.create({
// 1. flexDirection varsayılanı 'column' (web'de 'row')
// (Default flexDirection is 'column', in web it's 'row')
column: {
flexDirection: 'column', // ← varsayılan (default)
},
row: {
flexDirection: 'row',
},
// 2. flex sadece sayı alır, 'flex: 1 0 auto' yok
// (flex only takes a number, no 'flex: 1 0 auto')
fillSpace: {
flex: 1,
},
// 3. position varsayılanı 'relative' (web ile aynı)
// position: 'absolute' var, ama 'fixed' yok
// (position: 'absolute' exists, but no 'fixed')
absolutePosition: {
position: 'absolute',
top: 0, left: 0, right: 0, bottom: 0,
},
// 4. gap desteği var (gap support is available)
withGap: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 8,
rowGap: 12,
columnGap: 8,
},
// 5. Yüzde değerleri string olarak (Percentage values as strings)
halfWidth: {
width: '50%',
height: '100%',
},
});Yaygın Düzen Kalıpları (Common Layout Patterns)
const layoutPatterns = StyleSheet.create({
// Dikey ortalama (Vertical centering)
centerVertical: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
// Üst-alt sabitleme (Top-bottom pinning)
spaceBetween: {
flex: 1,
justifyContent: 'space-between',
},
// Yatay satır, ortala (Horizontal row, centered)
rowCenter: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
// Kart düzeni (Card layout)
card: {
backgroundColor: '#fff',
borderRadius: 12,
padding: 16,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 4,
},
// SafeArea — çentikli cihazlar için (for notched devices)
safeArea: {
flex: 1,
paddingTop: 0, // SafeAreaView kullan (use SafeAreaView)
},
});Platform.select — Platforma Özel Stil (Platform-Specific Styles)
import { Platform, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
container: {
...Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
android: {
elevation: 4,
},
}),
},
text: {
fontFamily: Platform.select({
ios: 'System',
android: 'Roboto',
}),
fontSize: 16,
// Platform kontrolü (Platform check)
paddingTop: Platform.OS === 'ios' ? 20 : 0,
},
});
// Platform versiyonu kontrolü (Platform version check)
if (Platform.OS === 'android' && Platform.Version >= 33) {
// Android 13+ özel mantık (Android 13+ specific logic)
}Boyutlar ve Responsive Tasarım (Dimensions and Responsive Design)
import { Dimensions, useWindowDimensions, PixelRatio } from 'react-native';
function ResponsiveComponent() {
// ✅ Hook — ekran boyutu değiştiğinde güncellenir (updates on screen resize)
const { width, height, fontScale } = useWindowDimensions();
const isTablet = width >= 768;
return (
<View style={{
flexDirection: isTablet ? 'row' : 'column',
padding: isTablet ? 32 : 16,
}}>
<Text style={{ fontSize: 16 / fontScale }}>Responsive metin</Text>
</View>
);
}
// Piksel yoğunluğu (Pixel density)
const pixelDensity = PixelRatio.get(); // 1, 1.5, 2, 3, 3.5
const fontScale = PixelRatio.getFontScale(); // Yazı tipi ölçeği
const dp = PixelRatio.roundToNearestPixel(18.3); // En yakın piksele yuvarla6) Navigasyon (Navigation)
Kurulum (Installation)
# React Navigation temel paketleri (core packages)
npm install @react-navigation/native
npm install @react-navigation/native-stack
npm install @react-navigation/bottom-tabs
npm install @react-navigation/drawer
# Expo bağımlılıkları (Expo dependencies)
npx expo install react-native-screens react-native-safe-area-context
# Bare workflow bağımlılıkları (Bare workflow dependencies)
npm install react-native-screens react-native-safe-area-context
# iOS: cd ios && pod installNavigasyon Tipleri (Navigation Types)
// src/navigation/types.ts
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import type { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
import type { CompositeScreenProps } from '@react-navigation/native';
// Stack navigasyon parametreleri (Stack navigation params)
export type RootStackParamList = {
Home: undefined;
Profile: { userId: string };
Settings: undefined;
ProductDetail: { productId: string; productName: string };
};
// Tab navigasyon parametreleri (Tab navigation params)
export type MainTabParamList = {
HomeTab: undefined;
SearchTab: undefined;
CartTab: undefined;
ProfileTab: undefined;
};
// Ekran prop tipleri (Screen prop types)
export type HomeScreenProps = NativeStackScreenProps<RootStackParamList, 'Home'>;
export type ProfileScreenProps = NativeStackScreenProps<RootStackParamList, 'Profile'>;
// İç içe navigasyon prop tipi (Nested navigation prop type)
export type HomeTabProps = CompositeScreenProps<
BottomTabScreenProps<MainTabParamList, 'HomeTab'>,
NativeStackScreenProps<RootStackParamList>
>;Stack Navigator
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import type { RootStackParamList } from './types';
const Stack = createNativeStackNavigator<RootStackParamList>();
function AppStack() {
return (
<Stack.Navigator
initialRouteName="Home"
screenOptions={{
headerStyle: { backgroundColor: '#007AFF' },
headerTintColor: '#fff',
headerTitleStyle: { fontWeight: 'bold' },
animation: 'slide_from_right',
headerBackTitle: 'Geri', // iOS geri butonu metni (iOS back button text)
}}
>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: 'Ana Sayfa', headerShown: false }}
/>
<Stack.Screen
name="Profile"
component={ProfileScreen}
options={({ route }) => ({ title: `Profil: ${route.params.userId}` })}
/>
<Stack.Screen
name="ProductDetail"
component={ProductDetailScreen}
options={{
presentation: 'modal', // Modal görünüm (Modal presentation)
animation: 'slide_from_bottom',
}}
/>
</Stack.Navigator>
);
}Bottom Tab Navigator
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons';
const Tab = createBottomTabNavigator<MainTabParamList>();
function MainTabs() {
return (
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
const icons: Record<string, string> = {
HomeTab: focused ? 'home' : 'home-outline',
SearchTab: focused ? 'search' : 'search-outline',
CartTab: focused ? 'cart' : 'cart-outline',
ProfileTab: focused ? 'person' : 'person-outline',
};
return <Ionicons name={icons[route.name] as any} size={size} color={color} />;
},
tabBarActiveTintColor: '#007AFF',
tabBarInactiveTintColor: '#999',
headerShown: false,
tabBarStyle: {
borderTopWidth: 0,
elevation: 10,
shadowOpacity: 0.1,
paddingBottom: 4,
height: 60,
},
})}
>
<Tab.Screen name="HomeTab" component={HomeScreen} options={{ title: 'Ana Sayfa' }} />
<Tab.Screen name="SearchTab" component={SearchScreen} options={{ title: 'Arama' }} />
<Tab.Screen
name="CartTab"
component={CartScreen}
options={{
title: 'Sepet',
tabBarBadge: 3, // Bildirim rozeti (Notification badge)
tabBarBadgeStyle: { backgroundColor: '#FF3B30' },
}}
/>
<Tab.Screen name="ProfileTab" component={ProfileScreen} options={{ title: 'Profil' }} />
</Tab.Navigator>
);
}Drawer Navigator
import { createDrawerNavigator } from '@react-navigation/drawer';
const Drawer = createDrawerNavigator();
function DrawerNavigation() {
return (
<Drawer.Navigator
screenOptions={{
drawerActiveBackgroundColor: '#e0f0ff',
drawerActiveTintColor: '#007AFF',
drawerLabelStyle: { fontSize: 16 },
headerShown: true,
}}
drawerContent={(props) => <CustomDrawerContent {...props} />}
>
<Drawer.Screen name="Home" component={HomeScreen} />
<Drawer.Screen name="Settings" component={SettingsScreen} />
</Drawer.Navigator>
);
}İç İçe Navigasyon (Nested Navigation)
import { NavigationContainer } from '@react-navigation/native';
function AppNavigator() {
const { isAuthenticated } = useAuth();
return (
<NavigationContainer>
{isAuthenticated ? (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="MainTabs" component={MainTabs} />
<Stack.Screen name="ProductDetail" component={ProductDetailScreen} />
<Stack.Screen name="Checkout" component={CheckoutScreen} />
</Stack.Navigator>
) : (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Register" component={RegisterScreen} />
<Stack.Screen name="ForgotPassword" component={ForgotPasswordScreen} />
</Stack.Navigator>
)}
</NavigationContainer>
);
}Navigasyon İşlemleri (Navigation Actions)
import { useNavigation, useRoute } from '@react-navigation/native';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
function HomeScreen() {
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
return (
<View>
{/* Ekrana git (Navigate to screen) */}
<Button title="Profil" onPress={() => navigation.navigate('Profile', { userId: '123' })} />
{/* Yığına ekle — her seferinde yeni ekran (Push — new screen each time) */}
<Button title="Push" onPress={() => navigation.push('Profile', { userId: '456' })} />
{/* Geri git (Go back) */}
<Button title="Geri" onPress={() => navigation.goBack()} />
{/* Kök ekrana git (Go to root) */}
<Button title="Köke git" onPress={() => navigation.popToTop()} />
{/* Parametreleri oku (Read params) */}
{/* Başka ekranda: route.params.userId */}
{/* Stack'i sıfırla — geri gidemez (Reset stack — can't go back) */}
<Button title="Sıfırla" onPress={() => {
navigation.reset({
index: 0,
routes: [{ name: 'Home' }],
});
}} />
</View>
);
}Deep Linking
// app.json (Expo)
{
"expo": {
"scheme": "myapp",
"android": {
"intentFilters": [{
"action": "VIEW",
"data": [{ "scheme": "myapp" }, { "scheme": "https", "host": "myapp.com" }],
"category": ["BROWSABLE", "DEFAULT"]
}]
},
"ios": {
"associatedDomains": ["applinks:myapp.com"]
}
}
}
// NavigationContainer linking yapılandırması (linking config)
const linking = {
prefixes: ['myapp://', 'https://myapp.com'],
config: {
screens: {
MainTabs: {
screens: {
HomeTab: 'home',
ProfileTab: 'profile',
},
},
ProductDetail: 'product/:productId',
Checkout: 'checkout',
},
},
};
function App() {
return (
<NavigationContainer linking={linking} fallback={<LoadingSpinner />}>
<AppNavigator />
</NavigationContainer>
);
}
// Kullanım (Usage): myapp://product/123 → ProductDetail ekranı açılır7) State Management
useState — Yerel Durum (Local State)
function Counter() {
const [count, setCount] = useState(0);
const [user, setUser] = useState<User | null>(null);
const [items, setItems] = useState<string[]>([]);
// Önceki değere göre güncelleme (Update based on previous value)
const increment = () => setCount(prev => prev + 1);
// Nesne güncelleme (Object update)
const updateUser = (name: string) =>
setUser(prev => prev ? { ...prev, name } : null);
// Dizi güncelleme (Array update)
const addItem = (item: string) => setItems(prev => [...prev, item]);
const removeItem = (index: number) =>
setItems(prev => prev.filter((_, i) => i !== index));
return <Text>{count}</Text>;
}useReducer — Karmaşık Durum (Complex State)
type State = { count: number; step: number; history: number[] };
type Action =
| { type: 'INCREMENT' }
| { type: 'DECREMENT' }
| { type: 'SET_STEP'; payload: number }
| { type: 'RESET' };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'INCREMENT':
return {
...state,
count: state.count + state.step,
history: [...state.history, state.count + state.step],
};
case 'DECREMENT':
return {
...state,
count: state.count - state.step,
history: [...state.history, state.count - state.step],
};
case 'SET_STEP':
return { ...state, step: action.payload };
case 'RESET':
return { count: 0, step: 1, history: [] };
default:
return state;
}
}
function CounterWithReducer() {
const [state, dispatch] = useReducer(reducer, { count: 0, step: 1, history: [] });
return (
<View>
<Text>Sayaç: {state.count}</Text>
<Button title="+" onPress={() => dispatch({ type: 'INCREMENT' })} />
</View>
);
}Context API — Global Durum (Global State)
// src/contexts/ThemeContext.tsx
type Theme = 'light' | 'dark';
interface ThemeContextType {
theme: Theme;
toggleTheme: () => void;
colors: typeof lightColors;
}
const lightColors = { bg: '#fff', text: '#000', primary: '#007AFF' };
const darkColors = { bg: '#1a1a1a', text: '#fff', primary: '#0A84FF' };
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>('light');
const toggleTheme = () => setTheme(prev => prev === 'light' ? 'dark' : 'light');
const colors = theme === 'light' ? lightColors : darkColors;
return (
<ThemeContext.Provider value={{ theme, toggleTheme, colors }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) throw new Error('useTheme must be used within ThemeProvider');
return context;
}Zustand — Hafif State Yönetimi (Lightweight State Management)
npm install zustand// src/store/useAuthStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
interface User {
id: string;
name: string;
email: string;
}
interface AuthState {
user: User | null;
token: string | null;
isAuthenticated: boolean;
login: (user: User, token: string) => void;
logout: () => void;
updateProfile: (updates: Partial<User>) => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
user: null,
token: null,
isAuthenticated: false,
login: (user, token) => set({
user,
token,
isAuthenticated: true,
}),
logout: () => set({
user: null,
token: null,
isAuthenticated: false,
}),
updateProfile: (updates) => set((state) => ({
user: state.user ? { ...state.user, ...updates } : null,
})),
}),
{
name: 'auth-storage',
storage: createJSONStorage(() => AsyncStorage),
}
)
);
// Kullanım (Usage)
function ProfileScreen() {
const { user, logout } = useAuthStore();
// Seçici kullanım — gereksiz render'ları önler
// (Selector usage — prevents unnecessary re-renders)
const userName = useAuthStore((state) => state.user?.name);
return (
<View>
<Text>{userName}</Text>
<Button title="Çıkış" onPress={logout} />
</View>
);
}Redux Toolkit
npm install @reduxjs/toolkit react-redux// src/store/slices/cartSlice.ts
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
interface CartItem { id: string; name: string; price: number; quantity: number; }
interface CartState { items: CartItem[]; loading: boolean; error: string | null; }
const initialState: CartState = { items: [], loading: false, error: null };
export const fetchCart = createAsyncThunk('cart/fetch', async (userId: string) => {
const response = await fetch(`/api/cart/${userId}`);
return response.json();
});
const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
addItem: (state, action: PayloadAction<CartItem>) => {
const existing = state.items.find(item => item.id === action.payload.id);
if (existing) {
existing.quantity += 1;
} else {
state.items.push({ ...action.payload, quantity: 1 });
}
},
removeItem: (state, action: PayloadAction<string>) => {
state.items = state.items.filter(item => item.id !== action.payload);
},
clearCart: (state) => { state.items = []; },
},
extraReducers: (builder) => {
builder
.addCase(fetchCart.pending, (state) => { state.loading = true; })
.addCase(fetchCart.fulfilled, (state, action) => {
state.loading = false;
state.items = action.payload;
})
.addCase(fetchCart.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message ?? 'Hata oluştu';
});
},
});
export const { addItem, removeItem, clearCart } = cartSlice.actions;
export default cartSlice.reducer;8) API Çağrıları (API Calls)
Fetch API
// src/services/api.ts
const BASE_URL = 'https://api.example.com';
async function request<T>(endpoint: string, options?: RequestInit): Promise<T> {
const token = useAuthStore.getState().token;
const response = await fetch(`${BASE_URL}${endpoint}`, {
headers: {
'Content-Type': 'application/json',
...(token && { Authorization: `Bearer ${token}` }),
...options?.headers,
},
...options,
});
if (response.status === 401) {
useAuthStore.getState().logout();
throw new Error('Oturum süresi doldu (Session expired)');
}
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(error.message || `HTTP ${response.status} hatası`);
}
return response.json();
}
export const api = {
get: <T>(endpoint: string) => request<T>(endpoint),
post: <T>(endpoint: string, data: unknown) =>
request<T>(endpoint, { method: 'POST', body: JSON.stringify(data) }),
put: <T>(endpoint: string, data: unknown) =>
request<T>(endpoint, { method: 'PUT', body: JSON.stringify(data) }),
delete: <T>(endpoint: string) =>
request<T>(endpoint, { method: 'DELETE' }),
};Axios ile API İstemcisi (API Client with Axios)
npm install axios// src/services/axiosClient.ts
import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios';
const axiosClient = axios.create({
baseURL: 'https://api.example.com',
timeout: 15000,
headers: { 'Content-Type': 'application/json' },
});
// İstek interceptor'ı (Request interceptor)
axiosClient.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const token = useAuthStore.getState().token;
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
},
(error) => Promise.reject(error)
);
// Yanıt interceptor'ı (Response interceptor)
axiosClient.interceptors.response.use(
(response) => response,
async (error: AxiosError) => {
if (error.response?.status === 401) {
useAuthStore.getState().logout();
}
return Promise.reject(error);
}
);
export default axiosClient;TanStack Query (React Query)
npm install @tanstack/react-query// src/providers/QueryProvider.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 2,
staleTime: 5 * 60 * 1000, // 5 dakika (5 minutes)
gcTime: 10 * 60 * 1000, // 10 dakika (10 minutes)
refetchOnWindowFocus: false, // Mobilde gereksiz (unnecessary on mobile)
refetchOnReconnect: true, // Bağlantı geri gelince yenile
},
},
});
export function QueryProvider({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
}
// Hook kullanımı (Hook usage)
import { useQuery, useMutation, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
function useUsers() {
return useQuery({
queryKey: ['users'],
queryFn: () => api.get<User[]>('/users'),
});
}
function useUser(id: string) {
return useQuery({
queryKey: ['users', id],
queryFn: () => api.get<User>(`/users/${id}`),
enabled: !!id, // id yoksa sorgu çalışmaz (query won't run if no id)
});
}
function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateUserDTO) => api.post<User>('/users', data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
onError: (error) => {
Alert.alert('Hata', error.message);
},
});
}
// Sonsuz kaydırma (Infinite scroll)
function useInfiniteUsers() {
return useInfiniteQuery({
queryKey: ['users', 'infinite'],
queryFn: ({ pageParam = 1 }) => api.get<UserPage>(`/users?page=${pageParam}`),
getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,
initialPageParam: 1,
});
}Yükleme ve Hata Durumları (Loading and Error States)
function UserListScreen() {
const { data: users, isLoading, error, refetch, isRefetching } = useUsers();
if (isLoading) return <FullScreenLoader visible />;
if (error) {
return (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>
Hata: {error.message} (Error: {error.message})
</Text>
<Button title="Tekrar Dene (Retry)" onPress={() => refetch()} />
</View>
);
}
return (
<FlatList
data={users}
renderItem={({ item }) => <UserCard user={item} />}
keyExtractor={(item) => item.id}
refreshing={isRefetching}
onRefresh={refetch}
ListEmptyComponent={
<Text style={styles.empty}>Kullanıcı bulunamadı (No users found)</Text>
}
/>
);
}9) Veri Saklama (Data Storage)
AsyncStorage — Basit Anahtar-Değer (Simple Key-Value)
npm install @react-native-async-storage/async-storageimport AsyncStorage from '@react-native-async-storage/async-storage';
// Kaydet (Store)
await AsyncStorage.setItem('username', 'fahri');
await AsyncStorage.setItem('settings', JSON.stringify({ theme: 'dark', lang: 'tr' }));
// Oku (Read)
const username = await AsyncStorage.getItem('username');
const settings = JSON.parse(await AsyncStorage.getItem('settings') || '{}');
// Sil (Remove)
await AsyncStorage.removeItem('username');
// Çoklu işlem (Multi operation)
await AsyncStorage.multiSet([['key1', 'val1'], ['key2', 'val2']]);
const values = await AsyncStorage.multiGet(['key1', 'key2']);
// Tüm anahtarlar (All keys)
const allKeys = await AsyncStorage.getAllKeys();
// Tümünü temizle (Clear all)
await AsyncStorage.clear();SecureStore — Güvenli Saklama (Secure Storage)
npx expo install expo-secure-storeimport * as SecureStore from 'expo-secure-store';
// ✅ Token, şifre, hassas veri için kullan (Use for tokens, passwords, sensitive data)
// iOS: Keychain, Android: EncryptedSharedPreferences
// Kaydet (Store)
await SecureStore.setItemAsync('authToken', 'eyJhbGciOiJIUzI1NiIs...');
// Oku (Read)
const token = await SecureStore.getItemAsync('authToken');
// Sil (Remove)
await SecureStore.deleteItemAsync('authToken');
// Seçenekler (Options)
await SecureStore.setItemAsync('biometric_key', 'secret', {
requireAuthentication: true, // Biyometrik doğrulama gerektir (Require biometric auth)
});
// Kullanılabilirlik kontrolü (Availability check)
const available = await SecureStore.isAvailableAsync();MMKV — Yüksek Performanslı Saklama (High-Performance Storage)
npm install react-native-mmkvimport { MMKV } from 'react-native-mmkv';
// MMKV örneği oluştur (Create MMKV instance)
const storage = new MMKV({ id: 'app-storage' });
// Senkron işlemler — AsyncStorage'dan çok daha hızlı
// (Synchronous operations — much faster than AsyncStorage)
storage.set('username', 'fahri');
storage.set('count', 42);
storage.set('isLoggedIn', true);
const username = storage.getString('username'); // string | undefined
const count = storage.getNumber('count'); // number | undefined
const isLoggedIn = storage.getBoolean('isLoggedIn'); // boolean | undefined
storage.delete('username');
storage.clearAll();
// Tüm anahtarlar (All keys)
const keys = storage.getAllKeys();
// Zustand ile MMKV persist (MMKV persist with Zustand)
import { StateStorage } from 'zustand/middleware';
const zustandStorage: StateStorage = {
getItem: (name) => storage.getString(name) ?? null,
setItem: (name, value) => storage.set(name, value),
removeItem: (name) => storage.delete(name),
};Depolama Karşılaştırması (Storage Comparison)
| Özellik (Feature) | AsyncStorage | SecureStore | MMKV |
|---|---|---|---|
| Performans | Yavaş (Slow) | Orta (Medium) | Çok hızlı (Very fast) |
| Şifreleme (Encryption) | ❌ | ✅ (OS-level) | İsteğe bağlı (Optional) |
| Senkron (Synchronous) | ❌ (async) | ❌ (async) | ✅ |
| Boyut sınırı (Size limit) | ~6MB (Android) | ~2KB/öğe | Sınırsız (Unlimited) |
| Kullanım alanı (Use case) | Ayarlar, önbellek | Token, şifre | Yüksek frekanslı veri |
| Expo desteği | ✅ | ✅ | ❌ (dev build gerekli) |
10) İzinler ve Cihaz Erişimi (Permissions & Device Access)
İzin Yönetimi (Permission Management)
# Expo ile (With Expo)
npx expo install expo-camera expo-location expo-notifications expo-media-library
# Bare workflow — react-native-permissions
npm install react-native-permissionsİzin Tablosu (Permission Table)
| İzin (Permission) | iOS (Info.plist) | Android (AndroidManifest.xml) | Expo Plugin |
|---|---|---|---|
| Kamera (Camera) | NSCameraUsageDescription | CAMERA | expo-camera |
| Galeri (Photo Library) | NSPhotoLibraryUsageDescription | READ_MEDIA_IMAGES | expo-media-library |
| Konum (Location) | NSLocationWhenInUseUsageDescription | ACCESS_FINE_LOCATION | expo-location |
| Bildirim (Notifications) | Otomatik (Automatic) | POST_NOTIFICATIONS (API 33+) | expo-notifications |
| Mikrofon (Microphone) | NSMicrophoneUsageDescription | RECORD_AUDIO | expo-av |
| Kişiler (Contacts) | NSContactsUsageDescription | READ_CONTACTS | expo-contacts |
| Takvim (Calendar) | NSCalendarsUsageDescription | READ_CALENDAR | expo-calendar |
| Biyometrik (Biometric) | NSFaceIDUsageDescription | USE_BIOMETRIC | expo-local-authentication |
Kamera Erişimi (Camera Access)
import { CameraView, useCameraPermissions } from 'expo-camera';
function CameraScreen() {
const [permission, requestPermission] = useCameraPermissions();
const [facing, setFacing] = useState<'front' | 'back'>('back');
const cameraRef = useRef<CameraView>(null);
if (!permission) return <View />;
if (!permission.granted) {
return (
<View style={styles.container}>
<Text>Kamera erişimi gerekli (Camera access required)</Text>
<Button title="İzin Ver (Grant Permission)" onPress={requestPermission} />
</View>
);
}
const takePicture = async () => {
if (cameraRef.current) {
const photo = await cameraRef.current.takePictureAsync({
quality: 0.8,
base64: false,
});
console.log('Fotoğraf URI:', photo?.uri);
}
};
return (
<View style={{ flex: 1 }}>
<CameraView ref={cameraRef} style={{ flex: 1 }} facing={facing}>
<View style={styles.cameraControls}>
<Pressable onPress={() => setFacing(f => f === 'back' ? 'front' : 'back')}>
<Text style={styles.controlText}>Çevir (Flip)</Text>
</Pressable>
<Pressable onPress={takePicture}>
<View style={styles.captureButton} />
</Pressable>
</View>
</CameraView>
</View>
);
}Konum Erişimi (Location Access)
import * as Location from 'expo-location';
function LocationScreen() {
const [location, setLocation] = useState<Location.LocationObject | null>(null);
useEffect(() => {
(async () => {
// İzin iste (Request permission)
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
Alert.alert('Hata', 'Konum izni reddedildi (Location permission denied)');
return;
}
// Mevcut konum (Current location)
const currentLocation = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.High,
});
setLocation(currentLocation);
})();
}, []);
// Konum takibi (Location tracking)
const startTracking = async () => {
await Location.watchPositionAsync(
{ accuracy: Location.Accuracy.High, distanceInterval: 10 },
(newLocation) => setLocation(newLocation)
);
};
return (
<View>
{location && (
<Text>
Enlem: {location.coords.latitude}, Boylam: {location.coords.longitude}
</Text>
)}
</View>
);
}Bildirimler (Notifications)
import * as Notifications from 'expo-notifications';
import { useEffect, useRef } from 'react';
// Bildirim yapılandırması (Notification configuration)
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
}),
});
function useNotifications() {
const notificationListener = useRef<Notifications.Subscription>();
useEffect(() => {
registerForPushNotifications();
// Bildirim dinleyicisi (Notification listener)
notificationListener.current = Notifications.addNotificationReceivedListener(
(notification) => {
console.log('Bildirim alındı:', notification.request.content);
}
);
return () => notificationListener.current?.remove();
}, []);
const registerForPushNotifications = async () => {
const { status } = await Notifications.requestPermissionsAsync();
if (status !== 'granted') return;
const token = (await Notifications.getExpoPushTokenAsync()).data;
console.log('Push token:', token);
// Token'ı backend'e gönder (Send token to backend)
};
// Yerel bildirim gönder (Send local notification)
const sendLocalNotification = async () => {
await Notifications.scheduleNotificationAsync({
content: {
title: 'Hatırlatma (Reminder)',
body: 'Uygulamayı kontrol etmeyi unutma! (Don\'t forget to check the app!)',
data: { screen: 'Home' },
},
trigger: { seconds: 5 },
});
};
return { sendLocalNotification };
}Info.plist ve AndroidManifest Yapılandırması (Configuration)
<!-- ios/MyApp/Info.plist — Expo app.json plugins ile otomatik eklenir -->
<key>NSCameraUsageDescription</key>
<string>Fotoğraf çekmek için kamera erişimi gereklidir.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Yakındaki yerleri göstermek için konum erişimi gereklidir.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Fotoğraf seçmek için galeri erişimi gereklidir.</string><!-- android/app/src/main/AndroidManifest.xml -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />11) Animasyon (Animation)
Animated API — Temel Animasyonlar (Basic Animations)
import { Animated, Easing } from 'react-native';
function FadeInView({ children }: { children: React.ReactNode }) {
const fadeAnim = useRef(new Animated.Value(0)).current;
const slideAnim = useRef(new Animated.Value(50)).current;
useEffect(() => {
Animated.parallel([
Animated.timing(fadeAnim, {
toValue: 1,
duration: 500,
useNativeDriver: true, // ✅ Her zaman true kullan (Always use true)
}),
Animated.timing(slideAnim, {
toValue: 0,
duration: 500,
easing: Easing.out(Easing.cubic),
useNativeDriver: true,
}),
]).start();
}, []);
return (
<Animated.View style={{
opacity: fadeAnim,
transform: [{ translateY: slideAnim }],
}}>
{children}
</Animated.View>
);
}
// Döngüsel animasyon (Looping animation)
function SpinningLoader() {
const spinValue = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.loop(
Animated.timing(spinValue, {
toValue: 1,
duration: 1000,
easing: Easing.linear,
useNativeDriver: true,
})
).start();
}, []);
const spin = spinValue.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg'],
});
return (
<Animated.View style={{ transform: [{ rotate: spin }] }}>
<Text style={{ fontSize: 32 }}>⟳</Text>
</Animated.View>
);
}
// Yay animasyonu (Spring animation)
function BounceButton({ onPress }: { onPress: () => void }) {
const scale = useRef(new Animated.Value(1)).current;
const handlePress = () => {
Animated.sequence([
Animated.spring(scale, { toValue: 0.9, useNativeDriver: true }),
Animated.spring(scale, { toValue: 1, friction: 3, useNativeDriver: true }),
]).start(() => onPress());
};
return (
<Animated.View style={{ transform: [{ scale }] }}>
<Pressable onPress={handlePress}>
<Text style={styles.buttonText}>Bas (Press)</Text>
</Pressable>
</Animated.View>
);
}Reanimated — Gelişmiş Animasyonlar (Advanced Animations)
npx expo install react-native-reanimated
# babel.config.js'e plugin ekle (add plugin to babel.config.js):
# plugins: ['react-native-reanimated/plugin']import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
withSpring,
withRepeat,
withSequence,
interpolate,
Easing,
runOnJS,
} from 'react-native-reanimated';
function ReanimatedExample() {
const offset = useSharedValue(0);
const opacity = useSharedValue(1);
const animatedStyles = useAnimatedStyle(() => ({
transform: [{ translateX: offset.value }],
opacity: opacity.value,
}));
const handlePress = () => {
// Zamanlama animasyonu (Timing animation)
offset.value = withTiming(200, {
duration: 500,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
});
// Yay animasyonu (Spring animation)
offset.value = withSpring(200, {
damping: 10,
stiffness: 100,
});
// Tekrarlayan animasyon (Repeating animation)
offset.value = withRepeat(
withTiming(200, { duration: 500 }),
-1, // Sonsuz tekrar (Infinite repeat)
true // Ters yön (Reverse)
);
// Sıralı animasyon (Sequential animation)
offset.value = withSequence(
withTiming(100, { duration: 200 }),
withTiming(-100, { duration: 200 }),
withSpring(0)
);
};
return (
<Animated.View style={[styles.box, animatedStyles]}>
<Pressable onPress={handlePress}>
<Text>Animate</Text>
</Pressable>
</Animated.View>
);
}
// Kaydırma bazlı animasyon (Scroll-based animation)
import Animated, { useAnimatedScrollHandler, useAnimatedStyle } from 'react-native-reanimated';
function AnimatedHeader() {
const scrollY = useSharedValue(0);
const scrollHandler = useAnimatedScrollHandler({
onScroll: (event) => {
scrollY.value = event.contentOffset.y;
},
});
const headerStyle = useAnimatedStyle(() => ({
height: interpolate(scrollY.value, [0, 100], [200, 60], 'clamp'),
opacity: interpolate(scrollY.value, [0, 100], [1, 0.8], 'clamp'),
}));
return (
<View style={{ flex: 1 }}>
<Animated.View style={[styles.header, headerStyle]} />
<Animated.ScrollView onScroll={scrollHandler} scrollEventThrottle={16}>
{/* İçerik (Content) */}
</Animated.ScrollView>
</View>
);
}Lottie — JSON Animasyonları (JSON Animations)
npx expo install lottie-react-nativeimport LottieView from 'lottie-react-native';
function SuccessAnimation() {
const animationRef = useRef<LottieView>(null);
return (
<LottieView
ref={animationRef}
source={require('../assets/animations/success.json')}
autoPlay
loop={false}
style={{ width: 200, height: 200 }}
speed={1.5}
onAnimationFinish={() => console.log('Animasyon bitti (Animation finished)')}
/>
);
}
// Kontrollü animasyon (Controlled animation)
function ControlledLottie() {
const progress = useSharedValue(0);
return (
<LottieView
source={require('../assets/animations/loading.json')}
progress={progress.value}
style={{ width: 150, height: 150 }}
/>
);
}Shared Element Transition
npm install react-native-shared-element react-navigation-shared-element// Expo Router ile shared transition (Shared transition with Expo Router)
import { SharedTransition, withLayoutAnimation } from 'react-native-reanimated';
// Liste ekranı (List screen)
function ListScreen() {
return (
<FlatList
data={items}
renderItem={({ item }) => (
<Link href={`/detail/${item.id}`} asChild>
<Pressable>
<Animated.Image
sharedTransitionTag={`image-${item.id}`}
source={{ uri: item.image }}
style={{ width: 100, height: 100 }}
/>
</Pressable>
</Link>
)}
/>
);
}
// Detay ekranı (Detail screen)
function DetailScreen({ id }: { id: string }) {
return (
<Animated.Image
sharedTransitionTag={`image-${id}`}
source={{ uri: item.image }}
style={{ width: '100%', height: 300 }}
/>
);
}12) Gesture — Dokunma Hareketleri (Touch Gestures)
Kurulum (Installation)
npx expo install react-native-gesture-handlerTemel Gesture'lar (Basic Gestures)
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';
// Sürükle-bırak (Drag and drop)
function DraggableBox() {
const translateX = useSharedValue(0);
const translateY = useSharedValue(0);
const savedX = useSharedValue(0);
const savedY = useSharedValue(0);
const panGesture = Gesture.Pan()
.onStart(() => {
savedX.value = translateX.value;
savedY.value = translateY.value;
})
.onUpdate((event) => {
translateX.value = savedX.value + event.translationX;
translateY.value = savedY.value + event.translationY;
})
.onEnd(() => {
// Yerine geri dön (Snap back) — isteğe bağlı
// translateX.value = withSpring(0);
// translateY.value = withSpring(0);
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{ translateX: translateX.value },
{ translateY: translateY.value },
],
}));
return (
<GestureDetector gesture={panGesture}>
<Animated.View style={[styles.box, animatedStyle]} />
</GestureDetector>
);
}
// Yakınlaştırma (Pinch to zoom)
function PinchableImage() {
const scale = useSharedValue(1);
const savedScale = useSharedValue(1);
const pinchGesture = Gesture.Pinch()
.onUpdate((event) => {
scale.value = savedScale.value * event.scale;
})
.onEnd(() => {
savedScale.value = scale.value;
if (scale.value < 1) {
scale.value = withSpring(1);
savedScale.value = 1;
}
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
return (
<GestureDetector gesture={pinchGesture}>
<Animated.Image
source={{ uri: 'https://example.com/photo.jpg' }}
style={[{ width: 300, height: 300 }, animatedStyle]}
/>
</GestureDetector>
);
}Swipe Aksiyonları (Swipe Actions)
import { Swipeable } from 'react-native-gesture-handler';
function SwipeableRow({ item, onDelete }: { item: Item; onDelete: () => void }) {
const renderRightActions = (progress: Animated.AnimatedInterpolation<number>) => {
const translateX = progress.interpolate({
inputRange: [0, 1],
outputRange: [100, 0],
});
return (
<Animated.View style={[styles.deleteAction, { transform: [{ translateX }] }]}>
<Pressable onPress={onDelete} style={styles.deleteButton}>
<Text style={styles.deleteText}>Sil (Delete)</Text>
</Pressable>
</Animated.View>
);
};
return (
<Swipeable renderRightActions={renderRightActions} overshootRight={false}>
<View style={styles.row}>
<Text>{item.name}</Text>
</View>
</Swipeable>
);
}Bottom Sheet
npm install @gorhom/bottom-sheetimport BottomSheet, { BottomSheetView, BottomSheetBackdrop } from '@gorhom/bottom-sheet';
function BottomSheetExample() {
const bottomSheetRef = useRef<BottomSheet>(null);
const snapPoints = useMemo(() => ['25%', '50%', '90%'], []);
return (
<View style={{ flex: 1 }}>
<Button
title="Sheet Aç (Open Sheet)"
onPress={() => bottomSheetRef.current?.expand()}
/>
<BottomSheet
ref={bottomSheetRef}
index={-1} // Başlangıçta kapalı (Initially closed)
snapPoints={snapPoints}
enablePanDownToClose
backdropComponent={(props) => (
<BottomSheetBackdrop {...props} disappearsOnIndex={-1} appearsOnIndex={0} />
)}
handleIndicatorStyle={{ backgroundColor: '#ccc' }}
>
<BottomSheetView style={{ padding: 16 }}>
<Text style={styles.sheetTitle}>Seçenekler (Options)</Text>
{/* İçerik (Content) */}
</BottomSheetView>
</BottomSheet>
</View>
);
}Pull-to-Refresh
// FlatList ile zaten yerleşik (Already built-in with FlatList)
function PullToRefreshList() {
const [refreshing, setRefreshing] = useState(false);
const onRefresh = useCallback(async () => {
setRefreshing(true);
await fetchData();
setRefreshing(false);
}, []);
return (
<FlatList
data={data}
renderItem={renderItem}
refreshing={refreshing}
onRefresh={onRefresh}
// Özel refresh göstergesi rengi (Custom refresh indicator color)
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
tintColor="#007AFF" // iOS
colors={['#007AFF']} // Android
progressBackgroundColor="#fff" // Android arka plan
/>
}
/>
);
}13) Native Modüller (Native Modules)
Kamera — expo-camera (Camera)
import { CameraView, CameraType, useCameraPermissions } from 'expo-camera';
function CameraModule() {
const [permission, requestPermission] = useCameraPermissions();
const [facing, setFacing] = useState<CameraType>('back');
const cameraRef = useRef<CameraView>(null);
const takePicture = async () => {
const photo = await cameraRef.current?.takePictureAsync({
quality: 0.7,
exif: true,
});
if (photo) {
// Fotoğrafı galeriye kaydet (Save photo to gallery)
await MediaLibrary.saveToLibraryAsync(photo.uri);
}
};
const recordVideo = async () => {
const video = await cameraRef.current?.recordAsync({ maxDuration: 30 });
console.log('Video URI:', video?.uri);
};
if (!permission?.granted) {
return <Button title="Kamera İzni Ver" onPress={requestPermission} />;
}
return (
<CameraView ref={cameraRef} style={{ flex: 1 }} facing={facing}>
<View style={styles.controls}>
<Pressable onPress={takePicture}><Text>Çek (Capture)</Text></Pressable>
<Pressable onPress={() => setFacing(f => f === 'back' ? 'front' : 'back')}>
<Text>Çevir (Flip)</Text>
</Pressable>
</View>
</CameraView>
);
}Biyometrik Kimlik Doğrulama (Biometric Authentication)
npx expo install expo-local-authenticationimport * as LocalAuthentication from 'expo-local-authentication';
async function authenticateWithBiometrics(): Promise<boolean> {
// Donanım kontrolü (Hardware check)
const hasHardware = await LocalAuthentication.hasHardwareAsync();
if (!hasHardware) {
Alert.alert('Hata', 'Biyometrik donanım bulunamadı (No biometric hardware found)');
return false;
}
// Kayıtlı biyometrik var mı? (Is biometric enrolled?)
const isEnrolled = await LocalAuthentication.isEnrolledAsync();
if (!isEnrolled) {
Alert.alert('Hata', 'Biyometrik kayıt bulunamadı (No biometric enrolled)');
return false;
}
// Desteklenen tipler (Supported types)
const supportedTypes = await LocalAuthentication.supportedAuthenticationTypesAsync();
// 1: Parmak izi (Fingerprint), 2: Yüz tanıma (Facial recognition), 3: Iris
// Kimlik doğrulama (Authenticate)
const result = await LocalAuthentication.authenticateAsync({
promptMessage: 'Kimliğinizi doğrulayın (Verify your identity)',
cancelLabel: 'İptal (Cancel)',
disableDeviceFallback: false, // Şifre ile giriş seçeneği (PIN fallback option)
fallbackLabel: 'Şifre Kullan (Use Password)',
});
return result.success;
}
// Kullanım (Usage)
function SecureScreen() {
const [authenticated, setAuthenticated] = useState(false);
useEffect(() => {
authenticateWithBiometrics().then(setAuthenticated);
}, []);
if (!authenticated) return <Text>Kimlik doğrulama gerekli (Auth required)</Text>;
return <Text>Güvenli içerik (Secure content)</Text>;
}Görsel Seçici (Image Picker)
npx expo install expo-image-pickerimport * as ImagePicker from 'expo-image-picker';
async function pickImage() {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [1, 1],
quality: 0.8,
});
if (!result.canceled) {
const uri = result.assets[0].uri;
// Görseli yükle (Upload image)
await uploadImage(uri);
}
}
async function takePhoto() {
const { status } = await ImagePicker.requestCameraPermissionsAsync();
if (status !== 'granted') return;
const result = await ImagePicker.launchCameraAsync({
allowsEditing: true,
quality: 0.8,
});
if (!result.canceled) {
console.log('Fotoğraf URI:', result.assets[0].uri);
}
}14) Platform-Specific Kod (Platform-Specific Code)
Platform.OS ile Koşullu Kod (Conditional Code with Platform.OS)
import { Platform } from 'react-native';
function PlatformSpecificComponent() {
return (
<View>
{Platform.OS === 'ios' ? (
<Text>iOS cihazdasınız (You are on iOS)</Text>
) : (
<Text>Android cihazdasınız (You are on Android)</Text>
)}
<Text style={{
fontFamily: Platform.OS === 'ios' ? 'Helvetica' : 'Roboto',
paddingTop: Platform.OS === 'ios' ? 44 : 0,
}}>
Platform bazlı stil (Platform-based style)
</Text>
</View>
);
}
// Platform.select — temiz yaklaşım (clean approach)
const styles = StyleSheet.create({
header: {
...Platform.select({
ios: { paddingTop: 44, borderBottomWidth: 0.5, borderBottomColor: '#ccc' },
android: { paddingTop: 0, elevation: 4 },
default: { paddingTop: 0 }, // Web veya diğer
}),
},
});Dosya Bazlı Platform Kodu (File-Based Platform Code)
src/components/
Button.tsx # Paylaşılan mantık (Shared logic)
Button.ios.tsx # iOS'a özel (iOS-specific)
Button.android.tsx # Android'e özel (Android-specific)
# React Native otomatik doğru dosyayı seçer
# (React Native automatically picks the correct file)
# import Button from './Button'; → iOS'ta .ios.tsx, Android'de .android.tsx// DatePicker.ios.tsx
import DateTimePicker from '@react-native-community/datetimepicker';
export function DatePicker({ value, onChange }: DatePickerProps) {
return (
<DateTimePicker
value={value}
mode="date"
display="spinner" // iOS tarzı (iOS style)
onChange={(_, date) => date && onChange(date)}
/>
);
}
// DatePicker.android.tsx
import DateTimePicker from '@react-native-community/datetimepicker';
export function DatePicker({ value, onChange }: DatePickerProps) {
const [show, setShow] = useState(false);
return (
<View>
<Pressable onPress={() => setShow(true)}>
<Text>{value.toLocaleDateString('tr-TR')}</Text>
</Pressable>
{show && (
<DateTimePicker
value={value}
mode="date"
display="default" // Android tarzı (Android style)
onChange={(_, date) => { setShow(false); date && onChange(date); }}
/>
)}
</View>
);
}15) CLI Komutları Tablosu (CLI Commands Table)
Expo CLI Komutları (Expo CLI Commands)
| Komut (Command) | Açıklama (Description) |
|---|---|
npx create-expo-app my-app | Yeni Expo projesi oluştur (Create new Expo project) |
npx expo start | Geliştirme sunucusu başlat (Start dev server) |
npx expo start --clear | Önbelleği temizleyerek başlat (Start with cache clear) |
npx expo start --dev-client | Dev client ile başlat (Start with dev client) |
npx expo start --tunnel | Tunnel modunda başlat (Start in tunnel mode) |
npx expo install <paket> | Uyumlu paket kur (Install compatible package) |
npx expo prebuild | Native projeler oluştur (Generate native projects) |
npx expo prebuild --clean | Temiz native proje oluştur (Clean native project) |
npx expo run:ios | iOS'ta çalıştır (Run on iOS) |
npx expo run:android | Android'de çalıştır (Run on Android) |
npx expo export | Web/embedded export (Web/embedded export) |
npx expo customize | Yapılandırma dosyaları oluştur (Generate config files) |
EAS CLI Komutları (EAS CLI Commands)
| Komut (Command) | Açıklama (Description) |
|---|---|
eas build --platform ios | iOS build oluştur (Create iOS build) |
eas build --platform android | Android build oluştur (Create Android build) |
eas build --profile preview | Preview build oluştur (Create preview build) |
eas submit --platform ios | App Store'a gönder (Submit to App Store) |
eas submit --platform android | Play Store'a gönder (Submit to Play Store) |
eas update | OTA güncelleme yayınla (Publish OTA update) |
eas device:create | Test cihazı kaydet (Register test device) |
eas credentials | Kimlik bilgilerini yönet (Manage credentials) |
React Native CLI Komutları (React Native CLI Commands)
| Komut (Command) | Açıklama (Description) |
|---|---|
npx react-native run-android | Android'de çalıştır (Run on Android) |
npx react-native run-ios | iOS'te çalıştır (Run on iOS) |
npx react-native start | Metro bundler başlat (Start Metro bundler) |
npx react-native start --reset-cache | Önbellek sıfırla (Reset cache) |
npx react-native log-android | Android loglarını göster (Show Android logs) |
npx react-native log-ios | iOS loglarını göster (Show iOS logs) |
npx react-native bundle | JS bundle oluştur (Create JS bundle) |
npx react-native info | Ortam bilgisi göster (Show environment info) |
Android & Gradle Komutları (Android & Gradle Commands)
| Komut (Command) | Açıklama (Description) |
|---|---|
cd android && ./gradlew assembleRelease | Release APK oluştur (Build release APK) |
cd android && ./gradlew bundleRelease | Release AAB oluştur (Build release AAB) |
cd android && ./gradlew clean | Build önbelleğini temizle (Clean build cache) |
adb devices | Bağlı cihazları listele (List connected devices) |
adb install app.apk | APK kur (Install APK) |
adb reverse tcp:8081 tcp:8081 | Port yönlendirme (Port forwarding) |
adb logcat *:E | Hata logları (Error logs) |
adb shell input keyevent 82 | Dev menüsü aç (Open dev menu) |
16) Guvenlik (Security)
Token Saklama (Token Storage)
// ❌ YANLIŞ — AsyncStorage şifrelenmez (WRONG — AsyncStorage is not encrypted)
await AsyncStorage.setItem('token', jwt);
// ✅ DOĞRU — SecureStore kullan (CORRECT — use SecureStore)
await SecureStore.setItemAsync('token', jwt);
// ✅ DOĞRU — MMKV şifreleme ile (CORRECT — MMKV with encryption)
const secureStorage = new MMKV({
id: 'secure-storage',
encryptionKey: 'your-encryption-key',
});SSL Pinning
# react-native-ssl-pinning ile (with react-native-ssl-pinning)
npm install react-native-ssl-pinningimport { fetch as sslFetch } from 'react-native-ssl-pinning';
const response = await sslFetch('https://api.example.com/data', {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
sslPinning: {
certs: ['server_cert'], // DER formatında sertifika (Certificate in DER format)
},
timeoutInterval: 10000,
});ProGuard / R8 — Android Kod Karıştırma (Android Code Obfuscation)
# android/app/proguard-rules.pro
# React Native kuralları (React Native rules)
-keep class com.facebook.hermes.unicode.** { *; }
-keep class com.facebook.jni.** { *; }
# Uygulama modelleri koru (Keep app models)
-keep class com.yourapp.models.** { *; }
# Reanimated
-keep class com.swmansion.reanimated.** { *; }// android/app/build.gradle
android {
buildTypes {
release {
minifyEnabled true // Kod küçültme (Code minification)
shrinkResources true // Kaynak küçültme (Resource shrinking)
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
}
}Hermes Engine Güvenliği (Hermes Engine Security)
// Hermes bytecode derler — kaynak kod APK'da görünmez
// (Hermes compiles bytecode — source code not visible in APK)
// Hermes aktif mi kontrol et (Check if Hermes is active)
const isHermes = () => !!(global as any).HermesInternal;
console.log('Hermes aktif:', isHermes());
// app.json Hermes yapılandırması (Hermes configuration)
// Expo SDK 49+ varsayılan olarak Hermes kullanır
// (Expo SDK 49+ uses Hermes by default)Genel Güvenlik Kontrol Listesi (General Security Checklist)
| Kontrol (Check) | Açıklama (Description) |
|---|---|
| SecureStore/Keychain kullan | Hassas veriler için (For sensitive data) |
| SSL Pinning uygula | MITM saldırılarına karşı (Against MITM attacks) |
| ProGuard/R8 etkinleştir | Kod karıştırma (Code obfuscation) |
| Hermes kullan | Bytecode derleme (Bytecode compilation) |
| Root/Jailbreak tespiti | Güvenli olmayan cihaz kontrolü (Compromised device check) |
| Ekran görüntüsü engelleme | FLAG_SECURE (Android) (Screenshot prevention) |
| Ağ güvenliği yapılandırması | network_security_config.xml (Network security config) |
| Hassas veri loglama | Üretimde console.log kaldır (Remove console.log in production) |
17) Tips — İpuçları ve Performans (Tips & Performance)
FlatList Optimizasyonu (FlatList Optimization)
// ✅ Optimizasyon ipuçları (Optimization tips)
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={(item) => item.id} // ✅ Benzersiz key (Unique key)
getItemLayout={(_, index) => ({ // ✅ Sabit yükseklik (Fixed height)
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
})}
initialNumToRender={10} // ✅ İlk render sayısı
maxToRenderPerBatch={5} // ✅ Batch render sayısı
windowSize={5} // ✅ Görünür pencere boyutu
removeClippedSubviews={true} // ✅ Görünmeyen öğeleri kaldır
updateCellsBatchingPeriod={50} // ✅ Batch güncelleme süresi (ms)
/>
// ✅ renderItem'ı memoize et (Memoize renderItem)
const renderItem = useCallback(({ item }: { item: Item }) => (
<MemoizedItemComponent item={item} />
), []);
// ✅ Bileşeni memo ile sar (Wrap component with memo)
const MemoizedItemComponent = React.memo(({ item }: { item: Item }) => (
<View style={styles.item}>
<Text>{item.name}</Text>
</View>
));FlashList — FlatList Alternatifi (FlatList Alternative)
npm install @shopify/flash-listimport { FlashList } from '@shopify/flash-list';
// FlatList'ten 5-10x daha performanslı (5-10x more performant than FlatList)
<FlashList
data={data}
renderItem={renderItem}
estimatedItemSize={80} // ✅ Tahmini öğe yüksekliği (Estimated item height)
keyExtractor={(item) => item.id}
/>Hermes Engine
// app.json — Expo'da varsayılan olarak aktif (Active by default in Expo)
{
"expo": {
"jsEngine": "hermes"
}
}Hermes avantajları (Hermes benefits):
- Daha hızlı uygulama başlatma (Faster app startup) — bytecode ön derleme
- Daha düşük bellek kullanımı (Lower memory usage)
- Daha küçük bundle boyutu (Smaller bundle size)
- Garbage collector optimizasyonu (GC optimization)
Bundle Boyutu Küçültme (Bundle Size Reduction)
# Bundle boyutu analizi (Bundle size analysis)
npx react-native-bundle-visualizer
# Expo ile bundle analizi
npx expo export --dump-sourcemap| Teknik (Technique) | Açıklama (Description) |
|---|---|
| Hermes kullan | Bytecode derleme ile küçültme (Reduce with bytecode compilation) |
| Tree shaking | Kullanılmayan kodları kaldır (Remove unused code) |
| Lazy import | React.lazy() ile dinamik import |
| Görsel optimizasyonu | WebP formatı, boyut sıkıştırma (WebP format, compression) |
| Gereksiz paketleri kaldır | depcheck ile kontrol et (Check with depcheck) |
| ProGuard/R8 | Android kod küçültme (Android code minification) |
| Moment.js yerine | date-fns veya dayjs kullan (Use date-fns or dayjs) |
| Lodash yerine | lodash-es veya native yöntemler (lodash-es or native methods) |
Performans İpuçları (Performance Tips)
// 1. ✅ useMemo — pahalı hesaplamaları önbelleğe al (Cache expensive computations)
const sortedItems = useMemo(
() => items.sort((a, b) => a.name.localeCompare(b.name)),
[items]
);
// 2. ✅ useCallback — fonksiyon referansını koru (Preserve function reference)
const handlePress = useCallback((id: string) => {
navigation.navigate('Detail', { id });
}, [navigation]);
// 3. ✅ React.memo — gereksiz render'ları önle (Prevent unnecessary renders)
const ExpensiveComponent = React.memo(({ data }: { data: Data }) => {
return <View>{/* Karmaşık UI */}</View>;
});
// 4. ✅ InteractionManager — ağır işlemleri geciktir (Defer heavy operations)
import { InteractionManager } from 'react-native';
useEffect(() => {
const task = InteractionManager.runAfterInteractions(() => {
// Animasyonlar bittikten sonra çalışır (Runs after animations complete)
loadHeavyData();
});
return () => task.cancel();
}, []);
// 5. ✅ Görsel önbelleğe alma (Image caching)
// expo-image veya react-native-fast-image kullan
import { Image } from 'expo-image';
<Image source={uri} cachePolicy="memory-disk" />
// 6. ✅ console.log üretimde kaldır (Remove console.log in production)
// babel.config.js
module.exports = {
plugins: [
['transform-remove-console', { exclude: ['error', 'warn'] }],
],
};
// 7. ✅ Hermes GC ayarlama — büyük listeler için
// (Hermes GC tuning — for large lists)
if (global.HermesInternal) {
global.HermesInternal.enablePromiseRejectionTracker?.();
}18) Test (Testing)
Jest — Birim Test (Unit Testing)
# Expo projesinde Jest zaten kurulu (Jest is already installed in Expo)
# Ek test kütüphaneleri (Additional test libraries)
npm install --save-dev @testing-library/react-native @testing-library/jest-native// package.json veya jest.config.js
{
"jest": {
"preset": "jest-expo",
"setupFilesAfterSetup": ["@testing-library/jest-native/extend-expect"],
"transformIgnorePatterns": [
"node_modules/(?!(react-native|@react-native|expo|@expo|@react-navigation)/)"
]
}
}// __tests__/utils/formatters.test.ts
import { formatCurrency, formatDate } from '../../src/utils/formatters';
describe('formatCurrency', () => {
it('TL formatında para birimi gösterir (displays currency in TL format)', () => {
expect(formatCurrency(1234.5)).toBe('₺1.234,50');
});
it('sıfır değer için doğru çalışır (works correctly for zero)', () => {
expect(formatCurrency(0)).toBe('₺0,00');
});
it('negatif değerleri işler (handles negative values)', () => {
expect(formatCurrency(-50)).toBe('-₺50,00');
});
});
describe('formatDate', () => {
it('tarihi Türkçe formatta gösterir (displays date in Turkish format)', () => {
const date = new Date('2024-03-15');
expect(formatDate(date)).toBe('15 Mart 2024');
});
});React Native Testing Library — Bileşen Testi (Component Testing)
// __tests__/components/LoginForm.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react-native';
import { LoginForm } from '../../src/components/LoginForm';
describe('LoginForm', () => {
const mockOnSubmit = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
});
it('giriş formunu render eder (renders login form)', () => {
render(<LoginForm onSubmit={mockOnSubmit} />);
expect(screen.getByPlaceholderText('E-posta')).toBeTruthy();
expect(screen.getByPlaceholderText('Şifre')).toBeTruthy();
expect(screen.getByText('Giriş Yap')).toBeTruthy();
});
it('form doğrulama hatalarını gösterir (shows form validation errors)', async () => {
render(<LoginForm onSubmit={mockOnSubmit} />);
fireEvent.press(screen.getByText('Giriş Yap'));
await waitFor(() => {
expect(screen.getByText('E-posta gerekli')).toBeTruthy();
expect(screen.getByText('Şifre gerekli')).toBeTruthy();
});
expect(mockOnSubmit).not.toHaveBeenCalled();
});
it('geçerli form gönderiminde onSubmit çağrılır (onSubmit called on valid submission)', async () => {
render(<LoginForm onSubmit={mockOnSubmit} />);
fireEvent.changeText(screen.getByPlaceholderText('E-posta'), 'test@example.com');
fireEvent.changeText(screen.getByPlaceholderText('Şifre'), 'password123');
fireEvent.press(screen.getByText('Giriş Yap'));
await waitFor(() => {
expect(mockOnSubmit).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123',
});
});
});
it('yükleme durumunda butonu devre dışı bırakır (disables button during loading)', () => {
render(<LoginForm onSubmit={mockOnSubmit} loading />);
const button = screen.getByText('Giriş Yapılıyor...');
expect(button).toBeDisabled();
});
});Hook Testi (Hook Testing)
// __tests__/hooks/useAuth.test.ts
import { renderHook, act } from '@testing-library/react-native';
import { useAuthStore } from '../../src/store/useAuthStore';
describe('useAuthStore', () => {
beforeEach(() => {
useAuthStore.setState({
user: null, token: null, isAuthenticated: false,
});
});
it('login kullanıcı bilgilerini günceller (login updates user info)', () => {
const { result } = renderHook(() => useAuthStore());
act(() => {
result.current.login(
{ id: '1', name: 'Fahri', email: 'fahri@example.com' },
'test-token'
);
});
expect(result.current.isAuthenticated).toBe(true);
expect(result.current.user?.name).toBe('Fahri');
expect(result.current.token).toBe('test-token');
});
it('logout durumu sıfırlar (logout resets state)', () => {
const { result } = renderHook(() => useAuthStore());
act(() => {
result.current.login(
{ id: '1', name: 'Fahri', email: 'fahri@example.com' },
'test-token'
);
result.current.logout();
});
expect(result.current.isAuthenticated).toBe(false);
expect(result.current.user).toBeNull();
});
});API Mock Testi (API Mock Testing)
// __tests__/services/userService.test.ts
import { api } from '../../src/services/api';
import { getUsers, createUser } from '../../src/services/userService';
jest.mock('../../src/services/api');
const mockedApi = api as jest.Mocked<typeof api>;
describe('userService', () => {
it('kullanıcı listesini getirir (fetches user list)', async () => {
const mockUsers = [
{ id: '1', name: 'Fahri', email: 'fahri@example.com' },
];
mockedApi.get.mockResolvedValueOnce(mockUsers);
const users = await getUsers();
expect(mockedApi.get).toHaveBeenCalledWith('/users');
expect(users).toEqual(mockUsers);
});
it('API hatası fırlatır (throws API error)', async () => {
mockedApi.get.mockRejectedValueOnce(new Error('Network error'));
await expect(getUsers()).rejects.toThrow('Network error');
});
});Detox — E2E Test (End-to-End Testing)
# Detox kurulumu (Detox installation)
npm install --save-dev detox @types/detox
npm install --save-dev jest-circus
# iOS build (macOS only)
detox build --configuration ios.sim.debug
# E2E testleri çalıştır (Run E2E tests)
detox test --configuration ios.sim.debug// e2e/login.test.ts
describe('Login Flow', () => {
beforeAll(async () => {
await device.launchApp();
});
beforeEach(async () => {
await device.reloadReactNative();
});
it('başarılı giriş akışı (successful login flow)', async () => {
// E-posta gir (Enter email)
await element(by.id('email-input')).typeText('test@example.com');
// Şifre gir (Enter password)
await element(by.id('password-input')).typeText('password123');
// Klavyeyi kapat (Dismiss keyboard)
await element(by.id('password-input')).tapReturnKey();
// Giriş butonuna bas (Press login button)
await element(by.id('login-button')).tap();
// Ana sayfa görünür mü kontrol et (Check if home screen is visible)
await waitFor(element(by.id('home-screen')))
.toBeVisible()
.withTimeout(5000);
// Hoş geldiniz mesajı (Welcome message)
await expect(element(by.text('Hoş Geldiniz'))).toBeVisible();
});
it('hatalı giriş durumu (invalid login scenario)', async () => {
await element(by.id('email-input')).typeText('wrong@example.com');
await element(by.id('password-input')).typeText('wrongpassword');
await element(by.id('login-button')).tap();
// Hata mesajı görünür mü (Is error message visible)
await waitFor(element(by.text('Geçersiz kimlik bilgileri')))
.toBeVisible()
.withTimeout(3000);
});
it('şifremi unuttum akışı (forgot password flow)', async () => {
await element(by.text('Şifremi Unuttum')).tap();
await waitFor(element(by.id('forgot-password-screen')))
.toBeVisible()
.withTimeout(2000);
await element(by.id('reset-email-input')).typeText('test@example.com');
await element(by.text('Sıfırlama Linki Gönder')).tap();
await waitFor(element(by.text('E-posta gönderildi')))
.toBeVisible()
.withTimeout(3000);
});
});Test Kapsamı (Test Coverage)
# Kapsam raporu ile test çalıştır (Run tests with coverage report)
npx jest --coverage
# Belirli dosya veya klasör (Specific file or folder)
npx jest --coverage --collectCoverageFrom='src/**/*.{ts,tsx}'
# İzleme modunda (Watch mode)
npx jest --watch// jest.config.js — kapsam eşikleri (coverage thresholds)
{
"coverageThreshold": {
"global": {
"branches": 70,
"functions": 80,
"lines": 80,
"statements": 80
}
}
}19) İlgili Rehberler (Related Guides)
| Rehber (Guide) | Açıklama (Description) |
|---|---|
| React Rehberi | React temelleri, Hooks, JSX (React fundamentals) |
| TypeScript Rehberi | TypeScript tip sistemi (TypeScript type system) |
| JavaScript/TypeScript Rehberi | JS/TS dil özellikleri (Language features) |
| CSS & Tailwind Rehberi | Flexbox temelleri (Flexbox basics) |
| Web Performans Rehberi | Performans optimizasyonu (Performance optimization) |
| Node.js Rehberi | Backend API geliştirme (Backend API development) |
| API Geliştirme Rehberi | REST/GraphQL API tasarımı (API design) |
| Docker Rehberi | CI/CD pipeline (CI/CD pipeline) |
| VS Code Rehberi | React Native eklentileri (RN extensions) |
Son güncelleme (Last updated): 2026-04 Bu rehber React Native 0.76+, Expo SDK 52+ ve React Navigation 7 baz alınarak hazırlanmıştır. (This guide is based on React Native 0.76+, Expo SDK 52+, and React Navigation 7.)