React Native - Mobile Anwendungsentwicklung im Jahr 2025
React Native Mobile
mobileReact Native – Mobile Anwendungsentwicklung
React Native ist eines der beliebtesten Frameworks zur Entwicklung mobiler Anwendungen für iOS und Android mit einer einzigen Codebasis in JavaScript und TypeScript. Das 2015 von Meta (ehemals Facebook) entwickelte Framework hat die mobile Entwicklung revolutioniert, indem es Web-Entwicklern ermöglicht, native Anwendungen zu erstellen, ohne Swift oder Kotlin von Grund auf lernen zu müssen. In diesem Artikel behandeln wir ausführlich die Architektur von React Native, die Werkzeuge, Entwurfsmuster und produktionsreife Best Practices.
Was ist React Native?#
React Native ist ein Open-Source-Framework, mit dem sich echte native mobile Anwendungen mit React erstellen lassen. Im Gegensatz zu hybriden Lösungen, die auf WebView basieren (wie Cordova/PhoneGap), rendert React Native native UI-Komponenten, die für die jeweilige Plattform spezifisch sind. Das bedeutet, dass ein Button in React Native ein echter UIButton auf iOS und ein android.widget.Button auf Android ist.
import React from 'react';
import { View, Text, StyleSheet, Platform } from 'react-native';
const WelcomeScreen: React.FC = () => {
return (
<View style={styles.container}>
<Text style={styles.title}>
Willkommen bei React Native!
</Text>
<Text style={styles.subtitle}>
Plattform: {Platform.OS} (v{Platform.Version})
</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 28,
fontWeight: 'bold',
color: '#333',
},
subtitle: {
fontSize: 16,
color: '#666',
marginTop: 8,
},
});
export default WelcomeScreen;
Die wichtigsten Merkmale von React Native sind die plattformübergreifende Codewiederverwendung (typischerweise 80-95% gemeinsamer Code), Hot Reloading für die sofortige Vorschau von Änderungen, ein reichhaltiges Ökosystem an Bibliotheken sowie eine große und aktive Entwickler-Community.
Expo vs Bare Workflow#
Bei der Arbeit mit React Native betrifft die erste Entscheidung die Wahl zwischen Expo und Bare Workflow. Jeder Ansatz hat seine Vorteile und Einschränkungen.
Expo – schneller Einstieg#
Expo ist ein Werkzeug- und Dienstleistungspaket, das rund um React Native aufgebaut ist und den Entwicklungsprozess erheblich vereinfacht. Das Expo SDK bietet fertige APIs für Kamera, Standort, Push-Benachrichtigungen und viele weitere Funktionen, ohne dass nativer Code konfiguriert werden muss.
// Projektinitialisierung mit Expo
// npx create-expo-app@latest MeineApp --template blank-typescript
import { StatusBar } from 'expo-status-bar';
import * as Location from 'expo-location';
import * as Camera from 'expo-camera';
import React, { useEffect, useState } from 'react';
import { View, Text, Alert } from 'react-native';
interface LocationCoords {
latitude: number;
longitude: number;
}
const LocationScreen: React.FC = () => {
const [location, setLocation] = useState<LocationCoords | null>(null);
useEffect(() => {
const getLocation = async () => {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
Alert.alert('Keine Berechtigung', 'Die App benötigt Zugriff auf den Standort');
return;
}
const currentLocation = await Location.getCurrentPositionAsync({});
setLocation({
latitude: currentLocation.coords.latitude,
longitude: currentLocation.coords.longitude,
});
};
getLocation();
}, []);
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
{location ? (
<Text>
Standort: {location.latitude.toFixed(4)}, {location.longitude.toFixed(4)}
</Text>
) : (
<Text>Standort wird ermittelt...</Text>
)}
<StatusBar style="auto" />
</View>
);
};
Ab Expo SDK 49+ ist auch der Expo Dev Client verfügbar, der die Verwendung benutzerdefinierter nativer Module bei gleichzeitigem Komfort von Expo ermöglicht. Dank Continuous Native Generation (CNG) generiert Expo die nativen iOS- und Android-Projekte automatisch basierend auf der Konfiguration in app.json.
Bare Workflow#
Der Bare Workflow bietet volle Kontrolle über den nativen Code von iOS (Xcode) und Android (Android Studio). Dies ist der bevorzugte Ansatz, wenn die Anwendung eine fortgeschrittene Integration mit nativen SDKs, benutzerdefinierte native Module oder spezifische Build-Konfigurationen erfordert.
# Initialisierung des Bare Workflow
npx react-native@latest init MeineApp --template react-native-template-typescript
# Projektstruktur
# ├── android/ <- nativer Android-Code
# ├── ios/ <- nativer iOS-Code
# ├── src/
# │ ├── components/
# │ ├── screens/
# │ ├── navigation/
# │ ├── store/
# │ ├── hooks/
# │ ├── services/
# │ └── types/
# ├── App.tsx
# ├── tsconfig.json
# └── package.json
In der Praxis deckt Expo mit dem Dev Client die meisten Anwendungsfälle ab und ist der empfohlene Ansatz für neue Projekte. Der Bare Workflow sollte nur bei sehr spezifischen nativen Anforderungen in Betracht gezogen werden.
React Native-Komponenten#
React Native bietet einen Satz grundlegender Komponenten, die auf native Views abgebildet werden. Die wichtigsten sind View, Text, Image, ScrollView, FlatList, TextInput, TouchableOpacity und Pressable.
import React, { useState } from 'react';
import {
View,
Text,
TextInput,
FlatList,
Pressable,
Image,
StyleSheet,
ActivityIndicator,
} from 'react-native';
interface Product {
id: string;
name: string;
price: number;
imageUrl: string;
description: string;
}
const PRODUCTS: Product[] = [
{
id: '1',
name: 'Laptop ProBook',
price: 4999,
imageUrl: 'https://example.com/laptop.jpg',
description: 'Leistungsstarker Laptop für die Arbeit',
},
{
id: '2',
name: 'Smartphone Ultra',
price: 2999,
imageUrl: 'https://example.com/phone.jpg',
description: 'Flaggschiff-Smartphone mit hervorragender Kamera',
},
];
interface ProductCardProps {
product: Product;
onPress: (product: Product) => void;
}
const ProductCard: React.FC<ProductCardProps> = ({ product, onPress }) => (
<Pressable
style={({ pressed }) => [
styles.card,
pressed && styles.cardPressed,
]}
onPress={() => onPress(product)}
>
<Image
source={{ uri: product.imageUrl }}
style={styles.productImage}
resizeMode="cover"
/>
<View style={styles.cardContent}>
<Text style={styles.productName}>{product.name}</Text>
<Text style={styles.productPrice}>{product.price} PLN</Text>
<Text style={styles.productDescription} numberOfLines={2}>
{product.description}
</Text>
</View>
</Pressable>
);
const ProductListScreen: React.FC = () => {
const [searchQuery, setSearchQuery] = useState('');
const [isLoading, setIsLoading] = useState(false);
const filteredProducts = PRODUCTS.filter((product) =>
product.name.toLowerCase().includes(searchQuery.toLowerCase())
);
const handleProductPress = (product: Product) => {
console.log('Produkt ausgewählt:', product.name);
};
return (
<View style={styles.container}>
<TextInput
style={styles.searchInput}
placeholder="Produkte suchen..."
value={searchQuery}
onChangeText={setSearchQuery}
autoCapitalize="none"
/>
{isLoading ? (
<ActivityIndicator size="large" color="#007AFF" />
) : (
<FlatList
data={filteredProducts}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<ProductCard product={item} onPress={handleProductPress} />
)}
ListEmptyComponent={
<Text style={styles.emptyText}>Keine Ergebnisse</Text>
}
contentContainerStyle={styles.listContent}
/>
)}
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#f8f9fa' },
searchInput: {
margin: 16,
padding: 12,
borderRadius: 8,
backgroundColor: '#fff',
borderWidth: 1,
borderColor: '#ddd',
fontSize: 16,
},
card: {
marginHorizontal: 16,
marginBottom: 12,
borderRadius: 12,
backgroundColor: '#fff',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
overflow: 'hidden',
},
cardPressed: { opacity: 0.9, transform: [{ scale: 0.98 }] },
productImage: { width: '100%', height: 200 },
cardContent: { padding: 16 },
productName: { fontSize: 18, fontWeight: '600', color: '#333' },
productPrice: { fontSize: 20, fontWeight: 'bold', color: '#007AFF', marginTop: 4 },
productDescription: { fontSize: 14, color: '#666', marginTop: 8 },
emptyText: { textAlign: 'center', fontSize: 16, color: '#999', marginTop: 32 },
listContent: { paddingBottom: 20 },
});
export default ProductListScreen;
FlatList ist besonders wichtig für die Leistung – im Gegensatz zu ScrollView rendert es nur die sichtbaren Listenelemente (Virtualisierung), was den Speicherverbrauch bei langen Listen erheblich reduziert.
Navigation mit React Navigation#
React Navigation ist die Standardbibliothek für die Navigation in React Native. Sie unterstützt Stack-Navigation, untere Tab-Navigation (Bottom Tabs), Drawer-Navigation sowie verschachtelte Navigatoren.
// npm install @react-navigation/native @react-navigation/native-stack
// npm install @react-navigation/bottom-tabs
// npm install react-native-screens react-native-safe-area-context
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { View, Text, Pressable, StyleSheet } from 'react-native';
// Typisierung der Navigationsparameter
type RootStackParamList = {
HomeTabs: undefined;
ProductDetail: { productId: string; productName: string };
Cart: undefined;
};
type HomeTabParamList = {
Products: undefined;
Favorites: undefined;
Profile: undefined;
};
const Stack = createNativeStackNavigator<RootStackParamList>();
const Tab = createBottomTabNavigator<HomeTabParamList>();
// Produktlisten-Bildschirm
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
type ProductsScreenProps = NativeStackScreenProps<RootStackParamList, 'HomeTabs'>;
const ProductsScreen: React.FC<ProductsScreenProps> = ({ navigation }) => (
<View style={styles.screen}>
<Text style={styles.title}>Produkte</Text>
<Pressable
style={styles.button}
onPress={() =>
navigation.navigate('ProductDetail', {
productId: '123',
productName: 'Laptop ProBook',
})
}
>
<Text style={styles.buttonText}>Produktdetails ansehen</Text>
</Pressable>
</View>
);
// Produktdetail-Bildschirm
type DetailScreenProps = NativeStackScreenProps<RootStackParamList, 'ProductDetail'>;
const ProductDetailScreen: React.FC<DetailScreenProps> = ({ route, navigation }) => {
const { productId, productName } = route.params;
return (
<View style={styles.screen}>
<Text style={styles.title}>{productName}</Text>
<Text>ID: {productId}</Text>
<Pressable
style={styles.button}
onPress={() => navigation.navigate('Cart')}
>
<Text style={styles.buttonText}>In den Warenkorb</Text>
</Pressable>
</View>
);
};
// Tab-Navigator
const HomeTabs: React.FC = () => (
<Tab.Navigator
screenOptions={{
tabBarActiveTintColor: '#007AFF',
tabBarInactiveTintColor: '#999',
}}
>
<Tab.Screen name="Products" component={ProductsScreen as any} />
<Tab.Screen name="Favorites" component={FavoritesScreen} />
<Tab.Screen name="Profile" component={ProfileScreen} />
</Tab.Navigator>
);
// Haupt-Navigator
const App: React.FC = () => (
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerStyle: { backgroundColor: '#007AFF' },
headerTintColor: '#fff',
headerTitleStyle: { fontWeight: 'bold' },
}}
>
<Stack.Screen
name="HomeTabs"
component={HomeTabs}
options={{ headerShown: false }}
/>
<Stack.Screen
name="ProductDetail"
component={ProductDetailScreen}
options={({ route }) => ({ title: route.params.productName })}
/>
<Stack.Screen name="Cart" component={CartScreen} />
</Stack.Navigator>
</NavigationContainer>
);
Die Typisierung der Navigationsparameter mit NativeStackScreenProps gewährleistet vollständige Typsicherheit – die IDE schlägt verfügbare Bildschirme und erforderliche Parameter bei jedem Aufruf von navigation.navigate() vor.
State Management#
In React Native können dieselben State-Management-Lösungen wie in React für das Web verwendet werden. Die beliebtesten Optionen sind Zustand, Redux Toolkit, Jotai sowie die integrierte Context API.
Zustand – leichtgewichtig und elegant#
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
interface CartStore {
items: CartItem[];
addItem: (item: Omit<CartItem, 'quantity'>) => void;
removeItem: (id: string) => void;
updateQuantity: (id: string, quantity: number) => void;
clearCart: () => void;
totalPrice: () => number;
totalItems: () => number;
}
export const useCartStore = create<CartStore>()(
persist(
(set, get) => ({
items: [],
addItem: (item) =>
set((state) => {
const existing = state.items.find((i) => i.id === item.id);
if (existing) {
return {
items: state.items.map((i) =>
i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
),
};
}
return { items: [...state.items, { ...item, quantity: 1 }] };
}),
removeItem: (id) =>
set((state) => ({
items: state.items.filter((i) => i.id !== id),
})),
updateQuantity: (id, quantity) =>
set((state) => ({
items: state.items.map((i) =>
i.id === id ? { ...i, quantity: Math.max(0, quantity) } : i
).filter((i) => i.quantity > 0),
})),
clearCart: () => set({ items: [] }),
totalPrice: () =>
get().items.reduce((sum, item) => sum + item.price * item.quantity, 0),
totalItems: () =>
get().items.reduce((sum, item) => sum + item.quantity, 0),
}),
{
name: 'cart-storage',
storage: createJSONStorage(() => AsyncStorage),
}
)
);
// Verwendung in einer Komponente
const CartBadge: React.FC = () => {
const totalItems = useCartStore((state) => state.totalItems());
return totalItems > 0 ? (
<View style={badgeStyles.badge}>
<Text style={badgeStyles.badgeText}>{totalItems}</Text>
</View>
) : null;
};
Zustand in Kombination mit AsyncStorage und der persist-Middleware bietet automatische Zustandspersistenz zwischen App-Starts. Dies ist besonders nützlich für den Warenkorb, Benutzereinstellungen oder Authentifizierungstoken.
Native Module#
Manchmal benötigt man Zugriff auf native Plattform-APIs, die in JavaScript nicht verfügbar sind. React Native ermöglicht die Erstellung nativer Module (Native Modules) und nativer Komponenten (Native Components).
// Beispiel für die Verwendung eines nativen Moduls – Biometrie
import ReactNativeBiometrics, { BiometryTypes } from 'react-native-biometrics';
const biometrics = new ReactNativeBiometrics();
interface AuthResult {
success: boolean;
error?: string;
}
const authenticateWithBiometrics = async (): Promise<AuthResult> => {
try {
const { available, biometryType } = await biometrics.isSensorAvailable();
if (!available) {
return { success: false, error: 'Biometrie auf diesem Gerät nicht verfügbar' };
}
const promptMessage =
biometryType === BiometryTypes.FaceID
? 'Identität mit Face ID bestätigen'
: 'Identität mit Fingerabdruck bestätigen';
const { success } = await biometrics.simplePrompt({
promptMessage,
cancelButtonText: 'Abbrechen',
});
return { success };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unbekannter Fehler',
};
}
};
// Eigenes TurboModule erstellen (New Architecture)
// specs/NativeDeviceInfo.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
getDeviceId(): string;
getBatteryLevel(): Promise<number>;
getAvailableStorage(): Promise<number>;
}
export default TurboModuleRegistry.getEnforcing<Spec>('DeviceInfo');
In der neuen Architektur von React Native ersetzen TurboModules die traditionellen Native Modules und bieten Lazy Loading, direkte Kommunikation mit nativem Code über JSI (JavaScript Interface) sowie Typisierung mittels CodeGen.
Hermes-Engine#
Hermes ist eine von Meta speziell für React Native entwickelte JavaScript-Engine. Ab Version 0.70 ist sie die Standard-Engine sowohl auf Android als auch auf iOS. Hermes kompiliert JavaScript während des Build-Prozesses in Bytecode (AOT – Ahead of Time), was messbare Vorteile bringt.
Die Hauptvorteile von Hermes sind eine deutlich schnellere App-Startzeit (TTI – Time to Interactive), geringerer RAM-Verbrauch, kleinere App-Größe sowie bessere Debugging-Unterstützung mit Chrome DevTools und Flipper.
// Prüfen, ob Hermes aktiv ist
const isHermesEnabled = (): boolean => {
return !!(global as any).HermesInternal;
};
// Hermes unterstützt moderne JS-Syntax,
// hat aber eingeschränkte Unterstützung für einige APIs
// Intl-Unterstützung erfordert zusätzliche Pakete
// npm install @formatjs/intl-pluralrules
// npm install @formatjs/intl-numberformat
import '@formatjs/intl-pluralrules/polyfill';
import '@formatjs/intl-numberformat/polyfill';
import '@formatjs/intl-numberformat/locale-data/de';
const formatPrice = (price: number): string => {
return new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR',
}).format(price);
};
New Architecture – Fabric und TurboModules#
Die React Native New Architecture ist ein grundlegender Umbau des Frameworks, der zwei Schlüsselelemente einführt: Fabric (neues Rendering-System) und TurboModules (neues System für native Module). Ab React Native 0.76 ist die neue Architektur standardmäßig aktiviert.
Fabric Renderer#
Fabric ersetzt das alte Rendering-System, das auf einer asynchronen Bridge basierte. Die wichtigsten Änderungen sind synchroner Zugriff auf die native Schicht über JSI (JavaScript Interface), Multithreaded-Rendering mit Priorisierung von UI-Updates, direkte Shadow-Tree-Erstellung in C++ sowie Unterstützung für Concurrent Features aus React 18.
JSI – JavaScript Interface#
// JSI eliminiert die Bridge und ermöglicht direkte Kommunikation
// JavaScript <-> C++ <-> Native (iOS/Android)
// Alte Architektur (Bridge):
// JS -> JSON serialize -> Bridge (async) -> JSON deserialize -> Native
// Latenz: ~5-10ms pro Aufruf
// Neue Architektur (JSI):
// JS -> JSI (sync, direct call) -> C++ -> Native
// Latenz: <1ms, synchrone Aufrufe
// Aktivierung der neuen Architektur in react-native.config.js
module.exports = {
project: {
android: {
unstable_reactLegacyComponentNames: [],
},
ios: {
unstable_reactLegacyComponentNames: [],
},
},
};
Die neue Architektur bringt erhebliche Verbesserungen in Bezug auf Leistung, Interaktivität und Reaktionsfähigkeit der Anwendung. Die Eliminierung der Bridge reduziert die Kommunikationslatenz zwischen JavaScript- und nativer Schicht um eine Größenordnung.
Leistungsoptimierung#
Leistung ist bei mobilen Anwendungen entscheidend. React Native bietet zahlreiche Optimierungstechniken, die einen flüssigen Betrieb bei 60 FPS ermöglichen.
import React, { useCallback, useMemo, memo } from 'react';
import { FlatList, View, Text, StyleSheet } from 'react-native';
// 1. Komponentenmemoization mit React.memo
interface ListItemProps {
id: string;
title: string;
onPress: (id: string) => void;
}
const ListItem = memo<ListItemProps>(({ id, title, onPress }) => {
const handlePress = useCallback(() => {
onPress(id);
}, [id, onPress]);
return (
<Pressable onPress={handlePress} style={styles.listItem}>
<Text style={styles.itemTitle}>{title}</Text>
</Pressable>
);
});
// 2. FlatList-Optimierung
interface DataItem {
id: string;
title: string;
category: string;
}
const OptimizedList: React.FC<{ data: DataItem[] }> = ({ data }) => {
// Stabile Referenz für renderItem
const renderItem = useCallback(
({ item }: { item: DataItem }) => (
<ListItem id={item.id} title={item.title} onPress={handleItemPress} />
),
[]
);
// Stabile Referenz für keyExtractor
const keyExtractor = useCallback((item: DataItem) => item.id, []);
// Optimierung von getItemLayout für feste Elementhöhen
const getItemLayout = useCallback(
(_: any, index: number) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
}),
[]
);
const handleItemPress = useCallback((id: string) => {
console.log('Gedrückt:', id);
}, []);
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={keyExtractor}
getItemLayout={getItemLayout}
// Leistungsparameter
removeClippedSubviews={true}
maxToRenderPerBatch={10}
windowSize={5}
initialNumToRender={10}
updateCellsBatchingPeriod={50}
/>
);
};
const ITEM_HEIGHT = 60;
// 3. Bildoptimierung mit react-native-fast-image
import FastImage from 'react-native-fast-image';
const OptimizedImage: React.FC<{ uri: string }> = ({ uri }) => (
<FastImage
source={{
uri,
priority: FastImage.priority.normal,
cache: FastImage.cacheControl.immutable,
}}
style={{ width: 200, height: 200 }}
resizeMode={FastImage.resizeMode.cover}
/>
);
// 4. Animationen mit React Native Reanimated (auf dem UI-Thread)
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming,
interpolate,
} from 'react-native-reanimated';
const AnimatedCard: React.FC = () => {
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
opacity: interpolate(scale.value, [0.95, 1], [0.8, 1]),
}));
const handlePressIn = () => {
scale.value = withSpring(0.95, { damping: 15, stiffness: 150 });
};
const handlePressOut = () => {
scale.value = withSpring(1, { damping: 15, stiffness: 150 });
};
return (
<Pressable onPressIn={handlePressIn} onPressOut={handlePressOut}>
<Animated.View style={[styles.card, animatedStyle]}>
<Text>Animierte Karte</Text>
</Animated.View>
</Pressable>
);
};
Die wichtigsten Leistungsprinzipien sind: Vermeidung anonymer Funktionen beim Rendern, Verwendung von useCallback und useMemo, Einsatz von react-native-reanimated anstelle der Animated API für komplexe Animationen, Aktivierung von removeClippedSubviews bei langen Listen sowie Bildoptimierung durch Caching und angemessene Größen.
Testen#
Das Testen von React Native-Anwendungen umfasst Unit-Tests, Integrationstests und End-to-End-Tests (E2E). Die am häufigsten verwendeten Tools sind Jest, React Native Testing Library und Detox.
// __tests__/CartStore.test.ts
import { useCartStore } from '../store/cartStore';
import { act, renderHook } from '@testing-library/react-hooks';
describe('CartStore', () => {
beforeEach(() => {
// Store vor jedem Test zurücksetzen
useCartStore.setState({ items: [] });
});
it('sollte ein Produkt zum Warenkorb hinzufügen', () => {
const { result } = renderHook(() => useCartStore());
act(() => {
result.current.addItem({
id: '1',
name: 'Laptop',
price: 4999,
});
});
expect(result.current.items).toHaveLength(1);
expect(result.current.items[0].quantity).toBe(1);
});
it('sollte die Menge eines vorhandenen Produkts erhöhen', () => {
const { result } = renderHook(() => useCartStore());
act(() => {
result.current.addItem({ id: '1', name: 'Laptop', price: 4999 });
result.current.addItem({ id: '1', name: 'Laptop', price: 4999 });
});
expect(result.current.items).toHaveLength(1);
expect(result.current.items[0].quantity).toBe(2);
});
it('sollte den Gesamtpreis korrekt berechnen', () => {
const { result } = renderHook(() => useCartStore());
act(() => {
result.current.addItem({ id: '1', name: 'Laptop', price: 4999 });
result.current.addItem({ id: '2', name: 'Telefon', price: 2999 });
});
expect(result.current.totalPrice()).toBe(7998);
});
});
// __tests__/ProductCard.test.tsx
import React from 'react';
import { render, fireEvent, screen } from '@testing-library/react-native';
import { ProductCard } from '../components/ProductCard';
describe('ProductCard', () => {
const mockProduct = {
id: '1',
name: 'Test Product',
price: 99,
imageUrl: 'https://example.com/image.jpg',
description: 'Testbeschreibung',
};
it('sollte den Produktnamen und -preis anzeigen', () => {
const mockOnPress = jest.fn();
render(<ProductCard product={mockProduct} onPress={mockOnPress} />);
expect(screen.getByText('Test Product')).toBeTruthy();
expect(screen.getByText('99 PLN')).toBeTruthy();
});
it('sollte onPress beim Klicken aufrufen', () => {
const mockOnPress = jest.fn();
render(<ProductCard product={mockProduct} onPress={mockOnPress} />);
fireEvent.press(screen.getByText('Test Product'));
expect(mockOnPress).toHaveBeenCalledWith(mockProduct);
});
});
// e2e/product.e2e.ts (Detox)
describe('Kaufablauf', () => {
beforeAll(async () => {
await device.launchApp();
});
it('sollte ein Produkt zum Warenkorb hinzufügen und zur Zusammenfassung navigieren', async () => {
await element(by.id('product-list')).scrollTo('bottom');
await element(by.id('product-card-1')).tap();
await element(by.id('add-to-cart-button')).tap();
await expect(element(by.id('cart-badge'))).toHaveText('1');
await element(by.id('cart-icon')).tap();
await expect(element(by.id('cart-total'))).toBeVisible();
});
});
Eine gute Testabdeckung ist entscheidend für die Stabilität mobiler Anwendungen, da jedes Update den Review-Prozess in den App Stores durchlaufen muss, was ein bis mehrere Tage dauern kann.
Deployment im App Store und Google Play#
Der Veröffentlichungsprozess für mobile Plattformen erfordert einige Konfigurationsschritte und die Erfüllung der Anforderungen der jeweiligen Stores.
// app.json (Expo) – Deployment-Konfiguration
{
"expo": {
"name": "MeineApp",
"slug": "meine-app",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"bundleIdentifier": "com.meinefirma.meineapp",
"buildNumber": "1",
"supportsTablet": true,
"infoPlist": {
"NSCameraUsageDescription": "Die App benötigt Zugriff auf die Kamera",
"NSLocationWhenInUseUsageDescription": "Wir benötigen den Standort, um die nächsten Geschäfte zu finden"
}
},
"android": {
"package": "com.meinefirma.meineapp",
"versionCode": 1,
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"permissions": ["CAMERA", "ACCESS_FINE_LOCATION"]
}
}
}
Build und Veröffentlichung mit EAS (Expo Application Services)#
# Installation der EAS CLI
npm install -g eas-cli
# Konfiguration der Build-Profile
# eas.json
# {
# "build": {
# "development": {
# "developmentClient": true,
# "distribution": "internal"
# },
# "preview": {
# "distribution": "internal",
# "ios": { "simulator": true }
# },
# "production": {
# "autoIncrement": true
# }
# },
# "submit": {
# "production": {
# "ios": { "appleId": "dev@firma.de", "ascAppId": "123456789" },
# "android": { "serviceAccountKeyPath": "./google-play-key.json" }
# }
# }
# }
# Production-Build
eas build --platform all --profile production
# Automatische Einreichung in die Stores
eas submit --platform ios --profile production
eas submit --platform android --profile production
# OTA (Over-the-Air) Updates ohne Store-Review
eas update --branch production --message "Preisanzeige-Korrektur"
EAS Update ermöglicht die Veröffentlichung von JavaScript-Updates, ohne erneut den Store-Review-Prozess durchlaufen zu müssen. Dies ist ein leistungsstarkes Werkzeug zur schnellen Fehlerbehebung und Bereitstellung kleinerer Änderungen.
React Native vs Flutter#
Der Vergleich von React Native mit Flutter ist eine der häufigsten Fragen bei der Wahl eines mobilen Frameworks. Beide Tools haben ihre Stärken.
React Native verwendet JavaScript/TypeScript, rendert native Plattformkomponenten, nutzt das riesige npm-Ökosystem und ist die natürliche Wahl für Teams mit React-Erfahrung. Die Community ist sehr groß, und die Unterstützung durch Meta ist aktiv.
Flutter verwendet Dart, verfügt über eine eigene Rendering-Engine (Skia/Impeller), bietet eine umfangreiche Widget-Bibliothek für Material und Cupertino und generiert kompilierten nativen Code. Flutter gewährleistet ein konsistenteres Erscheinungsbild zwischen den Plattformen, auf Kosten einer geringeren „Natürlichkeit" der UI.
| Aspekt | React Native | Flutter | |--------|-------------|---------| | Sprache | TypeScript/JavaScript | Dart | | UI | Native Plattformkomponenten | Eigene Widgets | | Leistung | Sehr gut (New Architecture) | Sehr gut (AOT-Kompilierung) | | Ökosystem | Riesig (npm) | Wachsend (pub.dev) | | Hot Reload | Ja | Ja | | App-Größe | ~7-15 MB | ~10-20 MB | | Lernkurve | Einfach (für React-Entwickler) | Mittel (neue Sprache) | | Web-Unterstützung | React Native Web (eingeschränkt) | Flutter Web (offiziell) |
Die Wahl zwischen React Native und Flutter hängt hauptsächlich von der Erfahrung des Teams, den Projektanforderungen und den Prioritäten bezüglich des App-Erscheinungsbilds ab. Wenn das Team bereits React kennt, ist React Native die natürliche Wahl. Wenn ein identisches Erscheinungsbild auf beiden Plattformen Priorität hat, kann Flutter die bessere Lösung sein.
Ökosystem und nützliche Bibliotheken#
React Native verfügt über ein reichhaltiges Ökosystem an Bibliotheken, die die Entwicklung beschleunigen. Hier sind die wichtigsten, nach Kategorien unterteilt.
Für die Navigation ist React Navigation oder Expo Router (dateibasiert, inspiriert von Next.js) der Standard. Für State Management sind Zustand, Redux Toolkit oder TanStack Query (ehemals React Query) für Serverdaten beliebt. Für Formulare eignet sich React Hook Form mit Zod zur Validierung. Animationen werden am besten mit React Native Reanimated und React Native Gesture Handler umgesetzt. Für das Styling lohnt es sich, NativeWind (Tailwind CSS für React Native) oder Tamagui in Betracht zu ziehen. Zur lokalen Datenspeicherung dienen MMKV (schneller als AsyncStorage) oder WatermelonDB (relationale Datenbank). Für die Netzwerkkommunikation ist Axios oder das native fetch mit TanStack Query am beliebtesten.
Zusammenfassung#
React Native ist ein ausgereiftes und bewährtes Framework zur Entwicklung mobiler Anwendungen, das die Erstellung nativer Apps für iOS und Android aus einer einzigen TypeScript-Codebasis ermöglicht. Die neue Architektur mit Fabric und TurboModules, die Hermes-Engine sowie das wachsende Ökosystem an Tools wie Expo machen React Native zu einer hervorragenden Wahl sowohl für Startups als auch für große Unternehmen.
Die entscheidenden Erfolgsfaktoren in React Native-Projekten sind die richtige Architekturwahl (Expo vs Bare Workflow), gute Praktiken beim State Management, Leistungsoptimierung von Anfang an sowie eine solide Teststrategie. Dank einer einzigen Codebasis sparen Sie Zeit und Ressourcen, und die Anwendung bietet auf beiden Plattformen ein natives Benutzererlebnis.
Benötigen Sie eine professionelle mobile Anwendung? Das Team von MDS Software Solutions Group ist auf die Entwicklung leistungsstarker mobiler Anwendungen mit React Native spezialisiert. Vom Prototyp bis zur Veröffentlichung in den Stores – wir begleiten Ihr Projekt durch den gesamten Entwicklungsprozess. Kontaktieren Sie uns und lassen Sie uns über Ihr Projekt sprechen!
Team von Programmierexperten, die sich auf moderne Webtechnologien spezialisiert haben.