Fabric und TurboModules ohne Bridge erklärt
Die Neue Architektur ersetzt die alte asynchrone Bridge mit JSON-Serialisierung durch JSI, ein direktes C++-Interface zwischen JavaScript und nativem Code. Fabric übernimmt als neuer Renderer die synchrone Layout-Berechnung, TurboModules laden native Module verzögert statt beim App-Start und Codegen erzeugt aus TypeScript-Spec-Dateien typsichere Schnittstellen für beide Plattformen.
Inhaltsverzeichnis
- 1. Die alte Bridge: warum JSON-Serialisierung zum Bottleneck wurde
- 2. JSI: das JavaScript Interface als Fundament der Neuen Architektur
- 3. Fabric: der neue Renderer statt UIManager und altem Shadow Tree
- 4. TurboModules: Lazy Loading statt eager NativeModules
- 5. Codegen: was aus der TypeScript-Spec-Datei wirklich entsteht
- 6. Native Bindings: die Swift-Implementierung eines TurboModules
- 7. Migrationspfad: newArchEnabled in einer bestehenden App
- 8. Typische Stolperstellen beim Umstieg auf die Neue Architektur
- 9. Alte Architektur und Neue Architektur im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Die alte Bridge: warum JSON-Serialisierung zum Bottleneck wurde
In der klassischen React-Native-Architektur liefen JavaScript-Thread, native UI-Thread und ein separater Shadow-Thread strikt getrennt nebeneinander. Kommunikation zwischen ihnen lief ausschließlich über die sogenannte Bridge: eine asynchrone Warteschlange, durch die jeder Aufruf, jedes Event und jede Layout-Änderung als JSON-String serialisiert, in Batches gesammelt und am Ende des Frames auf die jeweils andere Seite übertragen wurde. Diese Entkopplung klang zunächst elegant, weil sie JavaScript vor direktem Zugriff auf native Threads schützte. In der Praxis erzeugte sie jedoch messbaren Overhead, denn jedes Objekt musste zunächst in einen String verwandelt und auf der Gegenseite wieder geparst werden, bevor überhaupt etwas passieren konnte.
Das eigentliche Problem war nicht nur die Serialisierung selbst, sondern die erzwungene Asynchronität. Wollte eine Komponente die tatsächliche Höhe eines nativen Views kennen, bevor sie eine Animation startet, musste sie auf eine Antwort über die Bridge warten, die erst im nächsten Frame ankam. Synchrone native Aufrufe waren architektonisch schlicht unmöglich, weil die Bridge keine Rückantwort im selben Tick garantieren konnte. Bei komplexen Listen, Gesten oder Animationen summierten sich diese Wartezeiten zu spürbaren Rucklern, die Entwickler mit Workarounds wie InteractionManager oder manuellem Batching notdürftig umgingen, statt das eigentliche Problem zu lösen.
Hinzu kam, dass alle nativen Module beim App-Start eager instanziiert wurden, unabhängig davon, ob die JavaScript-Seite sie überhaupt jemals benötigte. Eine App mit vierzig eingebundenen Bibliotheken initialisierte vierzig native Module beim Kaltstart, selbst wenn nur fünf davon auf dem aktuellen Screen gebraucht wurden. Genau diese drei Schwachstellen, die JSON-Bridge, die erzwungene Asynchronität und das eager Loading, bildeten den Ausgangspunkt für die Neue Architektur, die JSI, Fabric und TurboModules gemeinsam adressieren.
// OLD ARCHITECTURE: everything crosses the async JSON bridge
import { NativeModules, findNodeHandle, UIManager } from 'react-native';
// A native call always returns via callback or promise, never synchronously
NativeModules.DeviceInfo.getBatteryLevel((error, level) => {
if (error) {
console.warn('Bridge call failed', error);
return;
}
console.log('Battery level:', level);
});
// Measuring a native view requires a round trip through the bridge,
// the result only arrives in a later frame, never in the current tick
UIManager.measure(findNodeHandle(myViewRef.current), (x, y, width, height) => {
// By the time this runs, the layout may already be stale
console.log('Measured width:', width);
});
2. JSI: das JavaScript Interface als Fundament der Neuen Architektur
JSI, kurz für JavaScript Interface, ist die zentrale Innovation, auf der die gesamte Neue Architektur aufbaut. Statt Daten über eine serialisierte Warteschlange zu schicken, gibt JSI der JavaScript-Engine, ob Hermes, JavaScriptCore oder V8, direkten Zugriff auf C++-Objekte im Speicher. Ein sogenanntes HostObject wird der JavaScript-Runtime als ganz normales JavaScript-Objekt präsentiert, seine Methoden rufen aber tatsächlich C++-Code auf, ohne dass jemals ein JSON-String erzeugt wird. Der Aufruf passiert synchron, im selben Thread und im selben Tick, genau wie ein normaler Funktionsaufruf innerhalb von JavaScript.
Diese Eigenschaft ist der entscheidende Unterschied zur alten Bridge: Wo vorher ein Promise oder Callback nötig war, um überhaupt eine Antwort aus dem nativen Layer zu bekommen, kann Code unter der Neuen Architektur einen nativen Wert direkt und synchron auslesen, sofern die Operation das zulässt. Das eröffnet ganz neue Muster, etwa das synchrone Lesen von persistiertem Storage vor dem ersten Render oder das direkte Messen eines View-Layouts ohne Frame-Verzögerung. JSI ist dabei kein React-Native-spezifisches Konstrukt, sondern eine allgemeine C++-Schicht, die auch von anderen Bibliotheken wie Reanimated genutzt wird, um eigene direkte Bindungen an die JavaScript-Runtime aufzubauen.
Wichtig ist, dass JSI selbst kein Threading-Modell vorschreibt. Synchron heißt hier: kein Umweg über eine serialisierte Warteschlange, nicht zwangsläufig derselbe Thread für jede Operation. Rechenintensive native Arbeit lässt sich weiterhin auf einen Hintergrund-Thread auslagern, nur die Kommunikation selbst verliert den Bridge-Overhead. Für Entwickler bedeutet das: Die Neue Architektur ist kein reines Performance-Feature, sondern verändert grundsätzlich, welche Interaktionsmuster zwischen JavaScript und nativem Code überhaupt möglich sind.
3. Fabric: der neue Renderer statt UIManager und altem Shadow Tree
Fabric ist der Renderer der Neuen Architektur und ersetzt den alten UIManager zusammen mit dem klassischen Shadow-Tree-Mechanismus. In der alten Architektur existierte der Shadow Tree, der die Layout-Berechnung mit Yoga übernimmt, als eigenständige Struktur auf einem separaten Thread, die Ergebnisse wurden anschließend asynchron über die Bridge an den UIManager auf der nativen Seite übertragen. Fabric löst diese Trennung auf, indem der Shadow Tree jetzt in C++ implementiert ist und sowohl von JavaScript als auch von der nativen Seite direkt über JSI angesprochen werden kann, ohne Bridge-Umweg.
Der praktische Effekt: Layout-Berechnungen können synchron ablaufen, bevor der native View tatsächlich gezeichnet wird, was Flackern und Layout-Sprünge reduziert, die in der alten Architektur durch die asynchrone Übertragung entstehen konnten. Fabric erlaubt außerdem, dass eine Komponente direkten Zugriff auf einen nativen View-Ref bekommt, dessen Eigenschaften synchron gemessen werden können, statt auf eine spätere Callback-Antwort zu warten. Für Bibliotheken, die eng mit nativen Views interagieren, etwa Kamera-Komponenten, Karten oder Video-Player, bedeutet das spürbar weniger Race Conditions zwischen JavaScript-State und tatsächlichem nativen Rendering-Zustand.
Fabric bringt außerdem die technische Grundlage für React-18-Features wie Concurrent Rendering und Suspense in React Native mit, weil der C++ Shadow Tree konsistente, konsistent commitbare Zustände zwischen mehreren gleichzeitigen Render-Durchläufen halten kann. Eigene native UI-Komponenten müssen dafür allerdings als Fabric-Components mit einem ComponentDescriptor neu registriert werden, ein alter UIManager-basierter Custom View läuft unter Fabric nicht automatisch weiter, sondern benötigt eine angepasste Implementierung auf beiden Plattformen.
4. TurboModules: Lazy Loading statt eager NativeModules
TurboModules sind der direkte Nachfolger der alten NativeModules-API und lösen das Problem des eager Loadings, bei dem jedes registrierte native Modul beim App-Start instanziiert wurde, egal ob JavaScript es je aufrief. Unter der Neuen Architektur wird ein TurboModule erst dann tatsächlich instanziiert, wenn JavaScript zum ersten Mal darauf zugreift, über eine JSI-vermittelte Lazy-Loading-Logik. Das reduziert die Startzeit besonders bei Apps mit vielen Drittanbieter-Bibliotheken spürbar, weil nicht mehr jedes potenziell ungenutzte Modul den kritischen Pfad des Kaltstarts blockiert.
Der zweite große Unterschied betrifft Typsicherheit. Alte NativeModules basierten auf lose typisierten Bridge-Aufrufen, Tippfehler in Methodennamen oder falsche Argumenttypen fielen erst zur Laufzeit auf, oft mit kryptischen Absturzmeldungen. TurboModules werden stattdessen über eine TypeScript- oder Flow-Spec-Datei definiert, aus der Codegen zur Build-Zeit sowohl die JavaScript-Schnittstelle als auch native Interfaces für iOS und Android erzeugt. Weicht die tatsächliche native Implementierung vom Spec ab, schlägt der Build fehl, nicht erst die App zur Laufzeit beim Nutzer.
// NativeDeviceInfo.ts — TurboModule spec file, read by Codegen at build time
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
// Synchronous methods are explicitly marked, Codegen enforces this contract
getBatteryLevelSync(): number;
getDeviceModel(): Promise<string>;
addListener(eventName: string): void;
removeListeners(count: number): void;
}
export default TurboModuleRegistry.get<Spec>('DeviceInfo') as Spec | null;
5. Codegen: was aus der TypeScript-Spec-Datei wirklich entsteht
Codegen ist das Werkzeug, das die TypeScript-Spec-Datei in tatsächlich ausführbaren, plattformspezifischen Code übersetzt, und genau das macht TurboModules und Fabric-Components erst praktisch nutzbar. Aus einer einzigen Spec-Datei generiert Codegen auf der C++-Seite abstrakte HostObject-Klassen, auf der iOS-Seite Objective-C++-Protokolle, die eine Swift-Klasse implementieren muss, und auf der Android-Seite Java- oder Kotlin-Interfaces mit den exakt passenden Methodensignaturen. Der Entwickler schreibt die eigentliche Logik nur einmal pro Plattform, den gesamten Bridging-Boilerplate übernimmt Codegen vollautomatisch.
Damit Codegen eine Spec-Datei überhaupt findet, muss das Paket im codegenConfig-Abschnitt des package.json referenziert werden, inklusive Angabe, welche Verzeichnisse gescannt werden und wie das generierte C++-Modul heißen soll. Bei Monorepos oder eigenständigen nativen Modulen läuft Codegen typischerweise während pod install auf iOS beziehungsweise während des Gradle-Builds auf Android automatisch, das generierte Ergebnis landet in einem build-Verzeichnis und wird nicht manuell versioniert. Ändert sich die Spec-Datei, etwa durch eine neue Methode, muss lediglich neu gebaut werden, keine manuelle Bridging-Datei muss von Hand angepasst werden.
{
"name": "react-native-device-info-turbo",
"version": "1.0.0",
"codegenConfig": {
"name": "DeviceInfoSpec",
"type": "modules",
"jsSrcsDir": "src",
"android": {
"javaPackageName": "com.mironsoft.deviceinfo"
},
"ios": {
"componentProvider": {}
}
},
"peerDependencies": {
"react-native": ">=0.74.0"
}
}
6. Native Bindings: die Swift-Implementierung eines TurboModules
Nachdem Codegen aus der Spec-Datei das passende Objective-C++-Protokoll erzeugt hat, muss auf der iOS-Seite eine konkrete Swift-Klasse dieses Protokoll implementieren. Der entscheidende Unterschied zur alten NativeModules-API: Die Methodensignaturen sind jetzt vom Compiler erzwungen, nicht mehr nur durch Konvention verbunden. Fehlt eine Methode oder stimmt ein Rückgabetyp nicht mit der Spec überein, bricht bereits die Kompilierung ab, lange bevor der Code auf einem echten Gerät läuft.
Auf der Android-Seite läuft dasselbe Prinzip über ein von Codegen generiertes Java-Interface, das eine Kotlin-Klasse implementiert, inklusive eines TurboReactPackage-Eintrags, der dem Framework mitteilt, wie das Modul lazy instanziiert werden soll. In beiden Fällen bleibt die eigentliche Geschäftslogik plattformspezifischer Swift- oder Kotlin-Code, nur die Anbindung an JavaScript läuft jetzt über den generierten, typsicheren JSI-Layer statt über manuell gepflegte Bridge-Methoden mit RCT_EXPORT_METHOD-Makros.
// DeviceInfoModule.swift — implements the Codegen-generated protocol
import Foundation
@objc(DeviceInfoModule)
class DeviceInfoModule: NSObject, NativeDeviceInfoSpec {
// Synchronous method, called directly through JSI, no bridge round trip
@objc func getBatteryLevelSync() -> NSNumber {
UIDevice.current.isBatteryMonitoringEnabled = true
return NSNumber(value: UIDevice.current.batteryLevel)
}
@objc func getDeviceModel(_ resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock) {
resolve(UIDevice.current.model)
}
@objc func addListener(_ eventName: String) {
// Required by the Spec even if not used for this module
}
@objc func removeListeners(_ count: NSNumber) {
// Required by the Spec even if not used for this module
}
}
7. Migrationspfad: newArchEnabled in einer bestehenden App
Der Umstieg auf die Neue Architektur in einer gewachsenen App beginnt nicht mit dem Umschalten eines Flags, sondern mit einer Bestandsaufnahme aller genutzten Drittanbieter-Bibliotheken. Für jede Abhängigkeit muss geklärt werden, ob sie bereits TurboModule- beziehungsweise Fabric-kompatibel ist, ob sie nur über die alte Bridge funktioniert, oder ob sie eine Interop-Schicht nutzt, die beide Architekturen gleichzeitig unterstützt. Das React-Native-Directory-Projekt listet diesen Status für die meisten populären Pakete, das erspart das manuelle Durchforsten einzelner Issue-Tracker.
Ist die Kompatibilität geklärt, folgt die technische Aktivierung. Auf Android genügt meist das Setzen von newArchEnabled=true in gradle.properties, auf iOS wird die Umgebungsvariable RCT_NEW_ARCH_ENABLED beim Ausführen von pod install gesetzt, wodurch CocoaPods die Fabric- und TurboModule-fähigen Varianten der Podspecs auswählt. Nach der Aktivierung sollte zunächst in einer Entwicklungsumgebung getestet werden, mit vollständigem Neustart der Metro-Bundler-Caches, da alte, unter der klassischen Architektur kompilierte native Artefakte sonst zu unklaren Laufzeitfehlern führen können.
Ein realistischer Migrationsplan arbeitet schrittweise: zunächst die Aktivierung in einem Feature-Branch, danach ein vollständiger Regressionstest der kritischsten User-Flows, insbesondere Listen mit vielen Items, Gesten, Kamera- und Kartenintegrationen sowie alle selbst geschriebenen nativen Module. Erst wenn diese Bereiche stabil laufen, wird die Neue Architektur in einem Staging-Build an ein breiteres internes Testteam ausgerollt, bevor sie produktiv für alle Nutzer aktiv wird.
#!/usr/bin/env bash
# Enable the New Architecture on both platforms and rebuild native projects
# Android: set the flag in gradle.properties
echo "newArchEnabled=true" >> android/gradle.properties
# iOS: install pods with the New Architecture flag set
cd ios
RCT_NEW_ARCH_ENABLED=1 pod install
cd ..
# Clear stale caches from the old architecture before the first run
watchman watch-del-all
rm -rf $TMPDIR/metro-* $TMPDIR/react-*
npx react-native start --reset-cache
8. Typische Stolperstellen beim Umstieg auf die Neue Architektur
Die häufigste Fehlerquelle sind selbst geschriebene native UI-Komponenten, die noch direkt auf den alten UIManager zugreifen. Unter Fabric existiert diese API in der bisherigen Form nicht mehr, ein Custom View muss stattdessen als Fabric-Component mit eigenem ComponentDescriptor registriert werden. Wird dieser Schritt übersprungen, rendert die Komponente entweder gar nicht oder wirft zur Laufzeit eine Fehlermeldung, die auf den ersten Blick nichts mit der eigentlichen Ursache zu tun zu haben scheint.
Eine zweite verbreitete Stolperstelle betrifft View Flattening: Fabric optimiert die native View-Hierarchie aggressiver als der alte Renderer, wodurch Refs auf verschachtelte Views unter Umständen ein anderes natives Objekt zurückgeben als erwartet. Code, der sich auf eine bestimmte View-Hierarchie-Tiefe verlässt, etwa für manuelles Messen oder Animationen außerhalb von Reanimated, muss entsprechend angepasst werden. Auch NativeEventEmitter-Patterns aus alten Bibliotheken, die Events noch über die klassische Bridge-Queue verschicken, funktionieren unter der Neuen Architektur oft nur über eine Kompatibilitätsschicht mit spürbarem Zusatzaufwand.
Drittens lohnt sich ein genauer Blick auf Timing-sensiblen Code: Weil Fabric Layout-Messungen synchron statt über einen späteren Bridge-Callback liefert, können Effekte, die bisher bewusst auf die Verzögerung der alten Architektur gewartet haben, unter der Neuen Architektur zu früh oder in anderer Reihenfolge ausgelöst werden. Solche Stellen fallen meist erst durch manuelles Durchklicken kritischer Screens auf, automatisierte Tests decken reine Timing-Regressionen selten vollständig ab.
9. Alte Architektur und Neue Architektur im direkten Vergleich
Die folgende Tabelle stellt die zentralen Unterschiede zwischen der klassischen Bridge-Architektur und der Neuen Architektur mit JSI, Fabric und TurboModules gegenüber. Sie zeigt, warum die Neue Architektur kein reines Marketing-Label ist, sondern jede der drei Komponenten ein konkretes, in der alten Architektur strukturell unlösbares Problem adressiert.
| Dimension | Alte Architektur | Neue Architektur |
|---|---|---|
| Kommunikationsschicht | Bridge mit JSON-Serialisierung | JSI: direkte C++-Bindungen |
| Native Aufrufe | nur asynchron via Callback/Promise | synchron möglich, wo sinnvoll |
| Modul-Laden | eager, alle Module beim Start | lazy via TurboModules |
| Renderer | UIManager + asynchroner Shadow Tree | Fabric mit C++ Shadow Tree |
| Typsicherheit | manuelle Bridging-Deklarationen | Codegen aus TypeScript-Spec |
Der Vergleich macht deutlich, dass die Neue Architektur weniger eine einzelne Funktion als vielmehr ein zusammenhängendes System ist. JSI liefert das Fundament, Fabric nutzt es für den Renderer, TurboModules nutzen es für native Module, und Codegen sorgt dafür, dass beide Seiten dabei denselben, zur Build-Zeit geprüften Vertrag einhalten. Wer nur eine der drei Komponenten isoliert betrachtet, unterschätzt, wie eng Fabric und TurboModules technisch auf JSI aufbauen.
Mironsoft
React-Native-Apps für Magento-Shops, headless über REST und GraphQL angebunden
Bereit für die Neue Architektur oder noch auf der alten Bridge?
Wir bauen performante React-Native-Storefronts, die per REST- und GraphQL-API mit eurem Magento-Shop sprechen, und begleiten bestehende Apps beim Umstieg auf Fabric und TurboModules, von der Bibliotheks-Analyse bis zum stabilen Rollout.
Architektur-Audit
Bibliotheks-Kompatibilität prüfen, Fabric- und TurboModule-Lücken identifizieren
Migrations-Begleitung
newArchEnabled schrittweise aktivieren, Regressionstests, stabiler Staging-Rollout
Native-Module-Entwicklung
TurboModules mit Codegen-Spec in Swift und Kotlin für Magento-Anbindungen bauen
10. Zusammenfassung
Die Neue Architektur in React Native löst drei konkrete, strukturelle Probleme der alten Bridge-Ära: JSI ersetzt die JSON-Serialisierung durch direkte, synchrone C++-Bindungen zwischen JavaScript und nativem Code. Fabric ersetzt UIManager und den alten asynchronen Shadow Tree durch einen gemeinsamen, in C++ implementierten Renderer, der Layout synchron berechnet und enger mit nativen Views interagiert. TurboModules ersetzen NativeModules durch lazy geladene, über Codegen typsicher generierte Schnittstellen, die Fehler bereits zur Build-Zeit statt erst beim Nutzer aufdecken.
Für bestehende Apps ist der Umstieg kein Ein-Klick-Schalter, sondern ein Prozess: Bibliotheks-Kompatibilität prüfen, newArchEnabled schrittweise in Entwicklungsumgebung und Staging aktivieren, kritische Screens mit Listen, Gesten und eigenen nativen Modulen gezielt testen. Wer diesen Pfad sauber durchläuft, profitiert von spürbar kürzeren Startzeiten, robusterem Layout-Timing und einem Typsystem, das native Bugs schon vor dem ersten App-Start verhindert.
Neue Architektur, Fabric und TurboModules — Das Wichtigste auf einen Blick
JSI statt Bridge
Direkte C++-Bindungen ersetzen JSON-Serialisierung, synchrone native Aufrufe werden technisch möglich.
Fabric statt UIManager
C++ Shadow Tree berechnet Layout synchron, engere Interop mit nativen Views, Basis für Concurrent Rendering.
TurboModules statt NativeModules
Lazy Loading beim ersten JS-Zugriff statt eager Instanziierung aller Module beim App-Start.
Codegen aus TypeScript-Spec
Ein Spec-File erzeugt C++, Swift- und Kotlin-Interfaces, Vertragsbrüche fallen beim Build auf, nicht beim Nutzer.