React NativeExpoMetroJSITurboModulesFabricCodegenHermesMobile Development

React Native + Expo + Metro + Native-Implementierung: Eine vollständige Anleitung

Sloth255
Sloth255
·8 min read·1,758 words

Wer mit React Native beginnt, hat schnell das Gefühl, dass „es irgendwie funktioniert", ohne wirklich zu verstehen warum. In diesem Artikel erläutern wir alles, was im Hintergrund passiert – von Metro, JSI, Codegen und den Expo-Mechanismen bis hin zur nativen Implementierung für Bereiche, die das Expo SDK nicht abdecken kann.


1. Die Grundphilosophie von React Native

Der Kern von React Native liegt darin, in JS geschriebene UI-Logik in native UI-Komponenten zu übersetzen. Genauso wie ReactDOM <div> in DOM-Elemente umwandelt, ordnet React Native <View> und <Text> auf iOS-UIView und Android-android.view.View zu.

Das bedeutet: Es wird echtes natives UI generiert – nicht HTML in einer WebView gerendert. Das ist der grundlegende Unterschied zu Cordova oder Ionic.


2. Architekturentwicklung: Von Bridge zu JSI

React Native hat derzeit zwei koexistierende Architekturen.

flowchart LR
  subgraph OLD["Alte Architektur (Bridge)"]
    direction TB
    J1["JS-Thread<br/>React-Logik"] -->|"JSON serialisieren"| BR["Bridge<br/>Asynchrone Warteschlange ⚠"]
    BR -->|"JSON deserialisieren"| N1["Nativer Thread<br/>UIKit / Android View"]
    SH["Shadow-Thread<br/>Yoga-Layout"] --> N1
  end
  subgraph NEW["Neue Architektur (JSI)"]
    direction TB
    HM["Hermes-Engine<br/>Bytecode-Ausführung"] -->|"Direkter C++-Verweis"| JSI["JSI<br/>Host Objects"]
    JSI --> FAB["Fabric<br/>Synchroner Renderer"]
    JSI --> TM["TurboModules<br/>Verzögerte Initialisierung"]
    FAB --> N2["Native Schicht"]
    TM --> N2
  end

Probleme der alten Architektur (Bridge)

Das alte React Native kommunizierte zwischen JS-Thread und nativem Thread über eine einzige asynchrone Bridge als Flaschenhals. Alle Austausche mussten als JSON-String serialisiert, gesendet und deserialisiert werden. Dieser Round-Trip führte bei häufigen Operationen wie Animationen und Gesten zu Leistungseinbußen.

Innovationen der neuen Architektur (JSI + Hermes)

JSI (JavaScript Interface) ist eine dünne Bindungsschicht in C++, die es der JS-Engine ermöglicht, native Objekte direkt und synchron zu referenzieren. Dadurch wurden folgende Verbesserungen möglich:

  • Fabric — Ein neuer Renderer, der UI-Bäume synchron aufbaut und aktualisiert
  • TurboModules — Verzögerte Initialisierung, die native Module erst bei Bedarf lädt
  • Hermes — Facebooks Engine, die JS in Bytecode vorkompiliert und die Startzeit drastisch reduziert

3. Codegen: Der Schlüssel zur typsicheren nativen Kommunikation

Codegen ist der am häufigsten übersehene Mechanismus in der neuen Architektur. Während JSI und TurboModules Kommunikation ermöglichen, garantiert Codegen zur Build-Zeit, dass sie korrekt abläuft.

flowchart TD
  SPEC["JS-Spec-Datei<br/>Typdefinitionen in TypeScript / Flow"]

  subgraph BUILD["Build-Zeit"]
    CG["Codegen<br/>Läuft bei pod install / Gradle"]
  end

  subgraph OUT["Generierte Artefakte"]
    CPP["Abstrakte C++-Klassen<br/>TurboModule-Basisschnittstellen"]
    DESC["Component Descriptor<br/>Für Fabric-native Komponenten"]
    JSTYPE["JS-Typdefinitionen<br/>.d.ts-Äquivalent"]
  end

  subgraph NATIVE["Native Implementierung"]
    IOS["iOS<br/>Implementiert in Swift / Obj-C"]
    AND["Android<br/>Implementiert in Kotlin / Java"]
  end

  RUNTIME["Über JSI<br/>Typsicher, synchrone Aufrufe"]

  SPEC --> CG
  CG --> CPP & DESC & JSTYPE
  CPP --> IOS & AND
  DESC --> IOS & AND
  IOS --> RUNTIME
  AND --> RUNTIME

In der alten Architektur war die Schnittstelle zwischen JS und Nativ im Wesentlichen ein „Gentleman's Agreement". Der Aufruf NativeModules.MyModule.doSomething(42) aus JS würde erst beim Laufzeit-Crash bemerkt werden, wenn die native Seite einen String erwartete.

Codegen löst dieses Problem zur Build-Zeit. Entwickler schreiben eine Spec (Spezifikation) in TypeScript oder Flow, die beschreibt, welche Typen ein natives Modul akzeptiert. Beim pod install (iOS) oder dem Gradle-Build (Android) generiert Codegen automatisch abstrakte C++-Klassen, natives Gerüst für iOS und Android und JS-Typdefinitionen.


4. Die Metro-Bundler-Pipeline

Metro ist Bundles React Natives dedizierter JavaScript-Bundler. Er erfüllt dieselbe Rolle wie webpack, ist aber für die mobile Entwicklung optimiert.

flowchart LR
  SRC[".js / .ts / .tsx<br/>Quelldateien"]
  RES["Auflösung<br/>Import-Pfad-Auflösung"]
  TRA["Transformation<br/>ES-Transforms via Babel"]
  SER["Serialisierung<br/>Bundle-Generierung"]
  DEV["Auslieferung ans Gerät<br/>HTTP :8081"]
  HMR["HMR<br/>Nur geänderte Module neu senden"]
  CACHE["FS-Cache<br/>Ab dem 2. Lauf schneller"]

  SRC --> RES --> TRA --> SER --> DEV
  TRA -.->|"Dateiänderung erkannt"| HMR
  HMR -.->|"Ohne State-Verlust reflektieren"| DEV
  TRA <-.->|"Transformierte Ergebnisse cachen"| CACHE

Metros Pipeline besteht aus drei Phasen:

  • Auflösung — Node.js-ähnliche Modulauflösung mit automatischer plattformspezifischer Dateiauswahl (foo.ios.ts / foo.android.ts)
  • Transformation — Babel konvertiert JSX, TypeScript und modernes ES; Ergebnisse werden im Cache gespeichert, sodass nachfolgende Starts schneller sind
  • Serialisierung — Generiert das finale Bundle und liefert es über einen HTTP-Server an das Gerät

Besonders hervorzuheben ist HMR (Hot Module Replacement). Nur der Graph der geänderten Module wird aktualisiert, sodass Codeänderungen sofort sichtbar werden, während der React-Komponentenzustand erhalten bleibt. Produktions-Builds kombinieren --minify mit der Hermes-Bytecode-Kompilierung und reduzieren so sowohl Dateigröße als auch Startzeit.


5. Expos Schichtenstruktur und Entwicklungswerkzeuge

flowchart TD
  APP["Ihre App<br/>App.tsx / screens / hooks"]
  SDK["Expo SDK<br/>expo-camera / expo-location / expo-router …"]
  EMC["expo-modules-core<br/>Typsicheres DSL in Swift / Kotlin"]
  RN["React Native Core<br/>View / Text / Animated / StyleSheet"]
  IOS["iOS<br/>UIKit / AVFoundation / CoreLocation"]
  AND["Android<br/>View / Camera2 / LocationManager"]
  GO["Expo Go<br/>Sofort per QR-Scan starten"]
  EASB["EAS Build<br/>.ipa / .apk in der Cloud generieren"]
  EASU["EAS Update<br/>Nur JS-Bundle per OTA ausliefern"]
  STORE["App Store / Google Play"]

  APP --> SDK
  EMC --> SDK
  SDK --> RN
  RN --> IOS & AND
  GO -->|"Während der Entwicklung hosten"| APP
  EASB --> STORE
  EASU -->|"Ohne App-Store-Review"| APP

Das Expo SDK stellt native Funktionen – Kamera, Standort, Benachrichtigungen, Dateisystem – als versionierte Pakete bereit. Kein eigener nativer Code nötig – das ist der größte Vorteil.

expo-modules-core ermöglicht die deklarative Entwicklung nativer Module mit Swift- und Kotlin-DSLs, integriert mit Codegen. Es ist typsicherer und weniger ausführlich als die alte NativeModules-Bridge, und fast alle Drittanbieter-SDKs von Expo nutzen es.

EAS (Expo Application Services) ist ein Sammelbegriff für Build, Update, Submit, Workflows und mehr. Dieser Artikel konzentriert sich auf EAS Build und EAS Update. EAS Build kompiliert native Binaries in der Cloud und ermöglicht iOS-Builds ohne Mac. EAS Update liefert JS-Bundles und Assets per OTA und ermöglicht schnelle Auslieferung von Änderungen, die keinen nativen Code berühren.


6. Bereiche, die Expo nicht abdeckt, und die Workflow-Wahl

Hauptbereiche, die das Expo SDK nicht abdeckt:

  • Bluetooth LE / NFC — Plattformspezifische Peripherie-Kommunikation
  • Eigene Payment-/Biometrie-SDKs — Direktintegration von Hersteller-.framework / .aar
  • Echtzeit-Audio/Video-Verarbeitung — Low-Level-WebRTC oder benutzerdefinierte Codec-Operationen
  • Interne native Bibliotheken — Swift / Kotlin-Assets aus bestehenden Apps
  • Benutzerdefinierte Kamera-Views — Benutzerdefiniertes Rendering mit OpenGL / Metal
flowchart TD
  START["Welche native Funktion brauchen Sie?"]

  START --> Q1{"Durch Expo SDK /<br/>öffentliche Bibliothek abgedeckt?"}
  Q1 -->|"Ja"| MANAGED["Managed Workflow<br/>Kein nativer Code nötig<br/>Nur npx expo start"]
  Q1 -->|"Nein"| Q2{"Nur Konfigurationsänderungen nötig?<br/>(Info.plist / AndroidManifest-Ergänzungen)"}
  Q2 -->|"Ja"| PLUGIN["Config Plugin erstellen<br/>In app.json eintragen<br/>Automatisch per prebuild angewendet"]
  Q2 -->|"Nein"| Q3{"Umfang des nativen Codes?"}
  Q3 -->|"Leichtgewichtig<br/>(vorhandenes SDK wrappen)"| BARE["Bare Workflow<br/>npx expo prebuild<br/>→ ios/ android/ direkt verwalten"]
  Q3 -->|"Umfangreich<br/>(eigenes Modul / UI)"| MODULE{"Was wird gebaut?"}
  MODULE -->|"Logik<br/>Bluetooth / Payment / Krypto"| TM["TurboModule<br/>+ Codegen"]
  MODULE -->|"UI-Komponente<br/>Benutzerdefinierte Kamera-View"| FAB["Fabric Component<br/>+ Codegen"]
  BARE --> TM & FAB
  PLUGIN --> BARE

Geringfügige Konfigurationsänderungen können mit Config Plugins abgedeckt werden. Falls Code geschrieben werden muss, ist der Wechsel zum Bare Workflow durch Generierung der ios/- und android/-Verzeichnisse mit npx expo prebuild der praktische Ausgangspunkt.


7. Gesamtarchitektur: JSI + Codegen + Native Implementierung

flowchart TB
  subgraph JS["JS-Schicht (Hermes)"]
    APP["App-Code / React-Komponenten"]
    SPEC["JS Spec<br/>(TypeScript + TurboModuleRegistry)"]
  end

  subgraph CODEGEN["Codegen (Automatisch zur Build-Zeit generiert)"]
    GEN_CPP["Abstrakte C++-Klassen<br/>TurboModule / ComponentDescriptor"]
    GEN_SHADOW["ShadowNode / Props-Definitionen"]
  end

  subgraph JSI_LAYER["JSI-Schicht (C++)"]
    JSI["JSI Host Object"]
    FABRIC["Fabric Renderer"]
  end

  subgraph NATIVE_IOS["iOS Native Implementierung"]
    SWIFT["Swift / Obj-C Implementierung<br/>(Erbt abstrakte C++-Klasse)"]
    SDK_IOS["Internes SDK / Externes .framework<br/>z.B. CoreBluetooth / Stripe"]
    UIVIEW["UIView-Unterklasse<br/>(Benutzerdefinierte UI-Komponente)"]
  end

  subgraph NATIVE_AND["Android Native Implementierung"]
    KOTLIN["Kotlin / Java Implementierung<br/>(Erbt abstrakte C++-Klasse)"]
    SDK_AND["Internes SDK / Externes .aar<br/>z.B. BluetoothGatt / Stripe"]
    ANDROIDVIEW["View-Unterklasse<br/>(Benutzerdefinierte UI-Komponente)"]
  end

  SPEC -->|"Typdefinitionen lesen"| CODEGEN
  GEN_CPP --> JSI
  GEN_SHADOW --> FABRIC
  APP -->|"Synchrone / asynchrone Aufrufe"| JSI
  JSI --> SWIFT & KOTLIN
  FABRIC --> UIVIEW & ANDROIDVIEW
  SWIFT --> SDK_IOS
  KOTLIN --> SDK_AND

Der Schlüssel liegt in der einseitigen Abhängigkeit: Spec → Codegen → Native Implementierung. Schreiben Sie Typdefinitionen auf der JS-Seite, generiert Codegen zur Build-Zeit abstrakte C++-Klassen. Swift / Kotlin-Implementierungen müssen diese nur erben – die Typkonsistenz wird vom Compiler garantiert und das „Gentleman's Agreement" als Ursache von Laufzeit-Crashes verschwindet.


8. TurboModule-Implementierungsfluss (Bluetooth-Beispiel)

Hinweis: Der folgende Code ist ein vereinfachtes Beispiel basierend auf den offiziellen React Native TurboModule / Codegen / Custom Events-Anleitungen. Es handelt sich nicht um direkte Zitate der offiziellen Beispiele, sondern um eine Neukonstruktion mit Bluetooth als Thema.

① JS Spec schreiben

// src/specs/NativeBluetoothModule.ts
import type { TurboModule, CodegenTypes } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export type ScanResult = {
  id: string;
  name: string;
  rssi: number;
};

export interface Spec extends TurboModule {
  startScan(serviceUUIDs: string[]): void;
  stopScan(): void;
  connect(deviceId: string): Promise<boolean>;
  readonly onDeviceFound: CodegenTypes.EventEmitter<ScanResult>;
}

export default TurboModuleRegistry.getEnforcing<Spec>('NativeBluetoothModule');

② Was Codegen generiert (Automatisch)

flowchart LR
  SPEC["NativeBluetoothModule.ts<br/>(Vom Entwickler geschrieben)"]

  subgraph AUTO["Automatisch generierter Code"]
    CPP["C++ Glue Code<br/>JSI / Codegen-Ausgabe"]
    IOS_H["iOS Glue Code<br/>Obj-C++ / Header-Ausgabe"]
    AND_J["Android Glue Code<br/>JNI / Spec-Klassen-Ausgabe"]
  end

  subgraph IMPL["Vom Entwickler implementiert"]
    SWIFT_IMPL["NativeBluetoothModule.swift<br/>Implementiert generierte Spec, ruft CoreBluetooth auf"]
    KOTLIN_IMPL["NativeBluetoothModule.kt<br/>Implementiert generierte Spec, ruft BluetoothGatt auf"]
  end

  SPEC --> CPP
  SPEC --> IOS_H
  SPEC --> AND_J
  CPP --> SWIFT_IMPL & KOTLIN_IMPL
  IOS_H --> SWIFT_IMPL
  AND_J --> KOTLIN_IMPL

③ iOS-Implementierung (Swift)

// ios/NativeBluetoothModule.swift
import CoreBluetooth

@objc(NativeBluetoothModule)
class NativeBluetoothModule: NativeBluetoothModuleSpec, CBCentralManagerDelegate {
  private var centralManager: CBCentralManager!

  override func startScan(_ serviceUUIDs: [String]) {
    let uuids = serviceUUIDs.map { CBUUID(string: $0) }
    centralManager.scanForPeripherals(withServices: uuids)
  }

  func centralManager(_ central: CBCentralManager,
                      didDiscover peripheral: CBPeripheral,
                      advertisementData: [String: Any], rssi: NSNumber) {
    emitOnDeviceFound([
      "id": peripheral.identifier.uuidString,
      "name": peripheral.name ?? "",
      "rssi": rssi
    ])
  }
}

④ Android-Implementierung (Kotlin)

// android/src/main/java/NativeBluetoothModule.kt
class NativeBluetoothModule(reactContext: ReactApplicationContext)
    : NativeBluetoothModuleSpec(reactContext), BluetoothGatt.Callback {

  private val adapter = BluetoothAdapter.getDefaultAdapter()

  override fun startScan(serviceUUIDs: ReadableArray) {
    val filters = serviceUUIDs.toArrayList().map { uuid ->
      ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString(uuid as String)).build()
    }
    adapter.bluetoothLeScanner.startScan(filters, ScanSettings.Builder().build(), scanCallback)
  }

  private val scanCallback = object : ScanCallback() {
    override fun onScanResult(callbackType: Int, result: ScanResult) {
      val params = Arguments.createMap().apply {
        putString("id", result.device.address)
        putString("name", result.device.name ?: "")
        putInt("rssi", result.rssi)
      }
      emitOnDeviceFound(params)
    }
  }
}

9. Fabric Component Implementierungsfluss (Benutzerdefiniertes Kamera-View-Beispiel)

Wenn TurboModule die Logik übernimmt, dient Fabric Component dazu, natives UI in JSX einzubetten. Das wird benötigt für benutzerdefiniertes Rendering mit OpenGL / Metal oder die Wiederverwendung bestehender nativer Views.

Hinweis: Das Folgende ist auch ein minimales Verständnisbeispiel. Codegen-Import-Pfade und Typnamen variieren je nach React Native-Version, daher die offizielle Dokumentation für die verwendete Version prüfen.

// src/specs/NativeCameraViewSpec.ts
import type { ViewProps } from 'react-native';
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
import type { DirectEventHandler, Float } from 'react-native/Libraries/Types/CodegenTypes';

type FrameCapturedEvent = Readonly<{
  uri: string;
}>;

interface NativeProps extends ViewProps {
  zoom?: Float;          // Codegen konvertiert Float → CGFloat / float
  torchEnabled?: boolean;
  onFrameCaptured?: DirectEventHandler<FrameCapturedEvent>;
}

export default codegenNativeComponent<NativeProps>('CameraView');

Codegen generiert aus dieser Spec C++-Definitionen für ShadowNode, ComponentDescriptor und Props. Auf iOS implementieren Sie eine CameraViewComponentView die RCTViewComponentView erbt, auf Android erbt sie von ReactViewGroup. Von der JS-Seite aus ist es so einfach wie <CameraView zoom={2.0} torchEnabled />.


10. Integration interner SDKs / externer .framework und .aar

iOS: Lokale Referenz über CocoaPods

# ios/Podfile
pod 'CompanyPaymentSDK', :path => '../vendor/ios/CompanyPaymentSDK'
# oder für xcframework
pod 'CompanyPaymentSDK', :podspec => '../vendor/ios/CompanyPaymentSDK.podspec'

Nach dem Hinzufügen zur Podfile und dem Ausführen von pod install kann es als normale Swift-Klasse mit import CompanyPaymentSDK verwendet werden. Direkt aus der TurboModule-Implementierung aufrufen.

Android: Lokales Maven / .aar-Referenz

// android/app/build.gradle
dependencies {
  implementation files('../vendor/android/company-payment-sdk.aar')
  // oder ein internes Maven-Repository
  implementation 'com.company:payment-sdk:1.4.0'
}

11. Die Entwicklungsschleife: Von Start bis Verifikation

flowchart TD
  INIT["npx create-expo-app MyApp<br/>--template blank-typescript"]

  subgraph START["npx expo start"]
    METRO["Metro Bundler startet<br/>Port 8081"]
    QR["QR-Code + Shortcuts<br/>i = iOS  a = Android  w = Web"]
  end

  subgraph DEVICE["Zielgerät wählen"]
    SIM["iOS Simulator<br/>In Xcode enthalten (nur Mac)"]
    EMU["Android Emulator<br/>Android Studio AVD"]
    GO["Physisches Gerät + Expo Go<br/>Über LAN oder Tunnel"]
  end

  subgraph NATIVE_BUILD["Nur beim ersten Mal: Nativer Build"]
    POD["pod install<br/>iOS CocoaPods"]
    GRADLE["Gradle Build<br/>Android"]
    CODEGEN_RUN["Codegen läuft<br/>Spec → Natives Gerüst"]
  end

  BUNDLE["JS Bundle ans Gerät übertragen<br/>über Metro HTTP-Server"]
  RENDER["Bildschirm wird angezeigt"]

  INIT --> START
  METRO --> QR
  QR --> SIM & EMU & GO
  SIM --> NATIVE_BUILD
  EMU --> NATIVE_BUILD
  NATIVE_BUILD --> BUNDLE
  GO --> BUNDLE
  BUNDLE --> RENDER

Mit npx expo start wird Metro gestartet, und im Terminal erscheint ein QR-Code mit Tastenkombinationen.

  • i-Taste → iOS Simulator (läuft auf Mac mit installiertem Xcode)
  • a-Taste → Android Emulator (virtuelles Gerät im Android Studio AVD Manager erstellt)
  • Für physische Geräte den QR-Code in Expo Go scannen – verbindet sich im LAN fast sofort

Beim expliziten Auslösen eines nativen Builds mit npx expo run:ios oder npx expo run:android läuft beim ersten Mal pod install (iOS) oder Gradle (Android), und Codegen generiert das native Gerüst. Spätere Läufe übertragen nur JS-Bundle-Differenzen und sind deutlich schneller.


12. Die Änderungsschleife im Bare Workflow

flowchart TD
  subgraph CHANGE["Verzweigung nach Änderungstyp"]
    JS_ONLY["Nur JS-Änderungen<br/>Komponenten / Logik / Styles"]
    NATIVE_CHANGE["Native Code-Änderungen<br/>Swift / Kotlin / Spec / Podfile"]
  end

  subgraph JS_LOOP["JS-Änderungsschleife (Schnell)"]
    METRO_HMR["Metro HMR<br/>Differenz-Bundle übertragen<br/>~Hunderte ms"]
    SIM_JS["Sofort im Emulator sichtbar<br/>App-Zustand erhalten"]
  end

  subgraph NATIVE_LOOP["Native Änderungsschleife (Langsam)"]
    CODEGEN_RUN["Codegen erneut ausführen<br/>pod install (iOS)<br/>Gradle sync (Android)"]
    REBUILD["Nativer Rebuild<br/>npx expo run:ios<br/>npx expo run:android"]
    SIM_NATIVE["Auf Emulator / Gerät überprüfen"]
  end

  JS_ONLY --> METRO_HMR --> SIM_JS
  NATIVE_CHANGE --> CODEGEN_RUN --> REBUILD --> SIM_NATIVE

  SIM_JS -->|"Keine Probleme"| DONE["Verifikation OK"]
  SIM_NATIVE -->|"Keine Probleme"| DONE
  SIM_JS -->|"Fehler gefunden"| DEBUG_JS["Hermes Debugger<br/>React DevTools"]
  SIM_NATIVE -->|"Nativer Crash"| DEBUG_NATIVE["Xcode / Android Studio<br/>Nativer Debugger"]
  DEBUG_JS --> JS_ONLY
  DEBUG_NATIVE --> NATIVE_CHANGE

Nur JS-Änderungen werden von Metro HMR innerhalb von Hunderten von Millisekunden als Differenz übertragen, wobei der Zustand erhalten bleibt.

Native Code-Änderungen erfordern einen Rebuild. Der erste iOS-Build dauert 2–5 Minuten, Differenzbuilds etwa 30 Sekunden bis eine Minute. Android mit Gradle-Cache ist ähnlich; der erste Build dauert länger.

Diese Asymmetrie ist der Schlüssel zur Entwicklungseffizienz im Bare Workflow: Die native Schnittstelle (Spec) frühzeitig fixieren und die meiste Implementierung auf der JS-Seite halten, um die Häufigkeit schwerer nativer Builds zu reduzieren.


13. Debugging: Untersuchung über die JS/Native-Grenze hinweg

flowchart TD
  SYMPTOM["Symptome beobachten"]

  SYMPTOM --> Q1{"Art des Crashes"}

  Q1 -->|"JS-Fehler<br/>LogBox-Rotbildschirm"| JS_ERR["Mit Hermes Debugger untersuchen<br/>Breakpoints, Stack Trace<br/>TS-Zeilennummern via Source Map"]
  Q1 -->|"Nativer Crash<br/>App friert ein / stürzt ab"| NAT_ERR["Crash-Logs prüfen"]
  Q1 -->|"Läuft, aber<br/>falsches Ergebnis"| BORDER["JS↔Native-Grenze verdächtig"]

  NAT_ERR --> IOS_CRASH["iOS: Xcode<br/>Debug Navigator<br/>+ lldb anhängen"]
  NAT_ERR --> AND_CRASH["Android: Android Studio<br/>Logcat Stack Trace<br/>+ Native Debugger"]

  BORDER --> LOG_IOS["iOS: NSLog / os_log<br/>npx react-native log-ios"]
  BORDER --> LOG_AND["Android: Log.d<br/>npx react-native log-android"]
  BORDER --> JS_LOG["JS-Seite: console.log<br/>TurboModule-Rückgabewerte prüfen"]

  IOS_CRASH --> INSTRUMENTS["Instruments<br/>Time Profiler / Allocations<br/>Speicherlecks / CPU identifizieren"]
  AND_CRASH --> PROFILER["Android Studio<br/>Memory / CPU Profiler"]

  JS_ERR --> FIXED["Beheben → Sofort via Metro HMR sichtbar"]
  INSTRUMENTS --> FIXED2["Beheben → Mit npx expo run:ios prüfen"]
  PROFILER --> FIXED3["Beheben → Mit npx expo run:android prüfen"]
  LOG_IOS & LOG_AND & JS_LOG --> BORDER2["Ursache identifizieren<br/>→ Spec / Implementierung beheben"]

JS-Fehler: Hermes Debugger

Cmd+D → „Open Debugger" öffnet Chrome DevTools. Metro stellt Source Maps bereit, sodass Breakpoints an originalen TypeScript-Zeilennummern gesetzt werden können – nicht im gebündelten Code. Ausnahmen bei TurboModule-Aufrufen zeigen den vollständigen Aufrufstapel bis zur JS-Seite.

Native Crashes: Xcode / Android Studio Debugger anhängen

# iOS: lldb an laufenden Simulator anhängen
npx expo run:ios --configuration Debug
# → Xcode öffnet sich automatisch und hängt den Debugger an

# Android
npx expo run:android
# → "Run > Attach Debugger to Android Process"

Bei Crashes, die nur auf physischen Geräten auftreten (besonders Bluetooth, Kamera, Sensoren) kann der Debugger auf die gleiche Weise an ein per USB verbundenes Gerät angehängt werden.

Grenzuntersuchung: Mit Logs einrahmen

Wenn Daten an der JS↔Native-Grenze beschädigt werden, ist das Einrahmen von beiden Seiten mit Logs der schnellste Ansatz.

// Swift-Seite
os_log("BluetoothModule.connect called: %{public}@", deviceId)
// → Mit npx react-native log-ios filtern
// Kotlin-Seite
Log.d("BluetoothModule", "connect called: $deviceId")
// → Mit npx react-native log-android filtern
// JS-Seite
const result = await BluetoothModule.connect(deviceId);
console.log('connect result:', result); // Ausgabe in Metro

React DevTools

npx react-devtools
# → Wartet auf Geräteverbindung auf Port 8097

Den Komponentenbaum visuell inspizieren und Props sowie State im Inspektor direkt bearbeiten und Änderungen in Echtzeit sehen.


14. Simulatoren vs. physische Geräte

Was getestet wird iOS Simulator Android Emulator Physisches Gerät
UI / Layout Ausreichend Ausreichend Abschlussverifikation
JS-Logik / API Ausreichend Ausreichend Nicht nötig
Bluetooth / NFC Nicht möglich Nicht möglich Erforderlich
Kamera / Mikrofon Teilweise Nicht möglich Erforderlich
Push-Benachrichtigungen Teilweise Teilweise Empfohlen
Performance Nur als Referenz Nur als Referenz Erforderlich
Biometrie Face ID-Sim verfügbar Fingerabdruck-Sim verfügbar Empfohlen

Für die Entwicklung mit nativen Modulen funktioniert ein zweistufiger Ansatz gut: JS-Verhalten auf Emulatoren festigen, native Funktionen auf physischen Geräten verifizieren. Besonders für Bluetooth und NFC – da Emulatoren sie gar nicht testen können – wirkt sich die Anzahl der Geräte und die OS-Versionsabdeckung direkt auf die Qualität aus.


Zusammenfassung

Der vollständige Technologie-Stack

Schicht Technologie Rolle
Bundler Metro Quell-Transpilierung, HMR, Plattformverzweigung
JS-Engine Hermes Bytecode-Ausführung für schnellen Start
JS↔Native-Bridge JSI Synchrone Bindung über C++
Code-Generierung Codegen Automatische Generierung typsicherer nativer Gerüste aus Spec
UI-Renderer Fabric Synchrones UI-Rendering auf JSI
Native APIs TurboModules Verzögert initialisierte Module, die Codegen-generierte Klassen erben
Entwicklungsinfrastruktur Expo / EAS SDK, Cloud-Builds, OTA-Updates

Vollständiger Ablauf für native Implementierung

① JS Spec schreiben (Typdefinitionen in TypeScript)
       ↓
② pod install / gradle build → Codegen generiert automatisch C++-Gerüst
       ↓
③ In Swift / Kotlin implementieren (internes SDK / externe Bibliotheken aufrufen)
       ↓
④ npx expo run:ios / run:android → Nativer Build & Emulator-Verifikation
       ↓
⑤ JS-Aufrufcode schreiben → Schnelle Iteration via Metro HMR
       ↓
⑥ JS-Fehler → Hermes Debugger
   Native Crashes → Xcode / Android Studio Debugger
   Grenz-Diskrepanzen → Mit log-ios / log-android + console.log einrahmen
       ↓
⑦ Bluetooth / Kamera / Sensoren auf physischen Geräten verifizieren
       ↓
⑧ EAS Build generiert Store-fertige Binaries

React Natives Wechsel von Bridge zu JSI + Codegen hat es von „funktioniert, aber fragil" zu „typsicher und hochperformant" transformiert. Das Verlassen von Expo bringt Kosten mit sich – schwere Builds und dieselbe Logik in zwei Sprachen – aber Codegen macht die Spec zur einzigen Quelle der Wahrheit und eliminiert das größte Risiko: Laufzeit-Crashes durch Typ-Diskrepanzen, bereits in der Designphase.


Referenz-URLs

Hinweis: Dieser Artikel fasst Informationen aus den folgenden Quellen zusammen und rekonstruiert sie. Beispiele und Erklärungen wurden zur besseren Verständlichkeit vereinfacht und sind keine direkten Zitate. Für die Implementierung immer die offizielle Dokumentation für die verwendete Version konsultieren.

Referenzierte URLs

React Native Offiziell

Expo Offiziell