Introduction
React Native's new architecture represents a fundamental reimagining of how the framework operates. At its core are two key innovations: Turbo Modules and Fabric. Together, they address long-standing performance bottlenecks and pave the way for a faster, more efficient React Native.
- Turbo Modules: A new native module system that loads modules lazily and enables synchronous communication
- Fabric: A complete rewrite of the UI rendering system with concurrent rendering capabilities
Turbo Modules: The New Native Module System
What Are Turbo Modules?
Turbo Modules replace the legacy native modules system with a more efficient architecture. Instead of loading all native modules at startup, Turbo Modules are loaded lazily when needed, dramatically reducing startup time.
Key Advantages
⚡ Lazy Loading
Modules are loaded on-demand rather than at startup, reducing initial bundle size and improving app launch time.
🔄 Synchronous Calls
Support for synchronous native method calls eliminates callback complexity for simple operations.
📘 Type Safety
Built-in TypeScript support with code generation ensures type safety across the JavaScript-Native bridge.
🚀 Better Performance
Optimized bridge communication reduces overhead and improves data serialization.
Creating a Turbo Module
Let's create a simple Turbo Module for device information:
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
// Synchronous method
getDeviceId(): string;
// Asynchronous method
getBatteryLevel(): Promise<number>;
// Method with parameters
logEvent(eventName: string, params: Object): void;
}
export default TurboModuleRegistry.getEnforcing<Spec>(
'DeviceInfo'
);
package com.myapp
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Promise
class DeviceInfoModule(reactContext: ReactApplicationContext) :
NativeDeviceInfoSpec(reactContext) {
override fun getName(): String {
return NAME
}
// Synchronous method
override fun getDeviceId(): String {
return android.provider.Settings.Secure.getString(
reactApplicationContext.contentResolver,
android.provider.Settings.Secure.ANDROID_ID
)
}
// Asynchronous method
@ReactMethod
override fun getBatteryLevel(promise: Promise) {
try {
val batteryManager = reactApplicationContext
.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
val level = batteryManager.getIntProperty(
BatteryManager.BATTERY_PROPERTY_CAPACITY
)
promise.resolve(level)
} catch (e: Exception) {
promise.reject("ERROR", e.message)
}
}
@ReactMethod
override fun logEvent(eventName: String, params: ReadableMap) {
// Implementation
}
companion object {
const val NAME = "DeviceInfo"
}
}
#import <React/RCTBridgeModule.h>
#import "RNDeviceInfoSpec.h"
@interface DeviceInfo : NSObject <NativeDeviceInfoSpec>
@end
@implementation DeviceInfo
RCT_EXPORT_MODULE()
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeDeviceInfoSpecJSI>(params);
}
// Synchronous method
- (NSString *)getDeviceId {
return [[[UIDevice currentDevice] identifierForVendor] UUIDString];
}
// Asynchronous method
- (void)getBatteryLevel:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
[[UIDevice currentDevice] setBatteryMonitoringEnabled:YES];
float batteryLevel = [[UIDevice currentDevice] batteryLevel];
if (batteryLevel < 0) {
reject(@"ERROR", @"Battery level unavailable", nil);
} else {
resolve(@(batteryLevel * 100));
}
}
- (void)logEvent:(NSString *)eventName params:(NSDictionary *)params {
// Implementation
}
@end
import DeviceInfo from './specs/NativeDeviceInfo';
function MyComponent() {
const [deviceId, setDeviceId] = useState('');
const [battery, setBattery] = useState(0);
useEffect(() => {
// Synchronous call
const id = DeviceInfo.getDeviceId();
setDeviceId(id);
// Asynchronous call
DeviceInfo.getBatteryLevel().then(level => {
setBattery(level);
});
// Log event
DeviceInfo.logEvent('screen_view', {
screen_name: 'Home'
});
}, []);
return (
<View>
<Text>Device ID: {deviceId}</Text>
<Text>Battery: {battery}%</Text>
</View>
);
}
Fabric: The New Rendering System
What is Fabric?
Fabric is React Native's new rendering engine that replaces the legacy UI Manager. It enables concurrent rendering, improves interop with native views, and provides better performance for complex UIs.
Key Advantages
🎯 Concurrent Rendering
Supports React 18's concurrent features like Suspense and automatic batching for smoother UIs.
⚙️ Simplified Architecture
Direct communication between JavaScript and native host views without multiple intermediate layers.
🔧 Better Native Interop
Improved integration with native views and easier embedding of React Native in existing apps.
📱 Cross-Platform Consistency
More consistent behavior between iOS and Android through shared C++ core.
Creating a Fabric Component
Here's how to create a custom native component using Fabric:
import type { ViewProps } from 'react-native';
import type { HostComponent } from 'react-native';
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
export interface NativeProps extends ViewProps {
title: string;
color?: string;
onPress?: () => void;
}
export default codegenNativeComponent<NativeProps>(
'CustomButton'
) as HostComponent<NativeProps>;
class CustomButtonView(context: Context) : AppCompatButton(context) {
private var onPressListener: (() -> Unit)? = null
init {
setOnClickListener {
onPressListener?.invoke()
}
}
fun setTitle(title: String) {
text = title
}
fun setColor(color: Int) {
setBackgroundColor(color)
}
fun setOnPress(listener: (() -> Unit)?) {
onPressListener = listener
}
}
class CustomButtonViewManager : SimpleViewManager<CustomButtonView>() {
override fun getName() = "CustomButton"
override fun createViewInstance(context: ThemedReactContext): CustomButtonView {
return CustomButtonView(context)
}
@ReactProp(name = "title")
fun setTitle(view: CustomButtonView, title: String) {
view.setTitle(title)
}
@ReactProp(name = "color", customType = "Color")
fun setColor(view: CustomButtonView, color: Int?) {
view.setColor(color ?: Color.BLUE)
}
override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> {
return mapOf(
"onPress" to mapOf("registrationName" to "onPress")
)
}
}
#import <React/RCTViewManager.h>
#import <React/RCTUIManager.h>
@interface CustomButtonViewManager : RCTViewManager
@end
@implementation CustomButtonViewManager
RCT_EXPORT_MODULE(CustomButton)
- (UIView *)view {
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
[button addTarget:self
action:@selector(buttonPressed:)
forControlEvents:UIControlEventTouchUpInside];
return button;
}
RCT_EXPORT_VIEW_PROPERTY(title, NSString)
RCT_EXPORT_VIEW_PROPERTY(color, UIColor)
RCT_EXPORT_VIEW_PROPERTY(onPress, RCTDirectEventBlock)
- (void)buttonPressed:(UIButton *)sender {
if (self.onPress) {
self.onPress(@{});
}
}
@end
import React from 'react';
import { StyleSheet } from 'react-native';
import CustomButtonNativeComponent from './CustomButtonNativeComponent';
interface Props {
title: string;
color?: string;
onPress?: () => void;
}
export default function CustomButton({ title, color, onPress }: Props) {
return (
<CustomButtonNativeComponent
style={styles.button}
title={title}
color={color}
onPress={onPress}
/>
);
}
const styles = StyleSheet.create({
button: {
height: 50,
paddingHorizontal: 20,
},
});
import CustomButton from './components/CustomButton';
function App() {
return (
<View style={styles.container}>
<CustomButton
title="Click Me!"
color="#667eea"
onPress={() => console.log('Button pressed!')}
/>
</View>
);
}
Enabling the New Architecture
To enable Turbo Modules and Fabric in your React Native project:
# Enable new architecture
newArchEnabled=true
# Enable new architecture
RCT_NEW_ARCH_ENABLED=1 pod install
Migration Considerations
- Audit existing native modules and components for compatibility
- Update third-party libraries to versions that support the new architecture
- Test thoroughly on both iOS and Android
- Consider gradual migration by enabling only for specific modules first
Performance Benefits
Real-world applications have reported significant improvements after migrating:
Startup Time
Up to 50% reduction in app initialization time due to lazy module loading
Frame Rate
Smoother animations and scrolling with concurrent rendering
Memory Usage
Lower memory footprint from optimized data structures
Bundle Size
Smaller initial bundles through code-splitting capabilities