Core Concepts & Architecture
Understanding React Native's architecture and core concepts is crucial for building efficient mobile applications. This section covers the fundamental concepts that differ from web development.
React Native Architecture
Bridge Architecture (Legacy)
The traditional React Native architecture uses a bridge to communicate between JavaScript and native code:
┌─────────────────┐ ┌─────────┐ ┌─────────────────┐
│ JavaScript │◄──►│ Bridge │◄──►│ Native │
│ Thread │ │ │ │ Thread │
│ │ │ │ │ │
│ • Business Logic│ │ Async │ │ • UI Rendering │
│ • State Mgmt │ │ JSON │ │ • Platform APIs │
│ • Component Tree│ │ Messages│ │ • Native Modules│
└─────────────────┘ └─────────┘ └─────────────────┘
Characteristics:
- Asynchronous communication via JSON messages
- Serialization overhead for data transfer
- Potential performance bottlenecks with frequent bridge crossing
New Architecture (Fabric + TurboModules)
The modern architecture introduces direct communication and improved performance:
┌─────────────────┐ ┌─────────────────┐
│ JavaScript │◄────JSI────►│ Native │
│ Thread │ │ Thread │
│ │ │ │
│ • React │ │ • Fabric │
│ • TurboModules │ │ • Native Modules│
│ • Direct Calls │ │ • UI Operations │
└─────────────────┘ └─────────────────┘
Key Improvements:
- JSI (JavaScript Interface): Direct JavaScript-to-native communication
- Fabric: New rendering system with synchronous UI updates
- TurboModules: Lazy-loaded native modules with better performance
- Codegen: Type-safe interfaces between JS and native code
Threading Model
React Native operates on multiple threads to maintain smooth performance:
Main Thread (UI Thread)
- Purpose: Handles UI rendering and user interactions
- Platform: Native iOS/Android UI operations
- Performance: Must stay responsive (60 FPS target)
// Operations that affect UI thread
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
},
})
JavaScript Thread
- Purpose: Executes your React code and business logic
- Operations: State updates, API calls, calculations
- Communication: Sends UI updates to main thread
// JavaScript thread operations
const [data, setData] = useState([])
useEffect(() => {
// API call runs on JS thread
fetchData().then(setData)
}, [])
Background Threads
- Purpose: Heavy operations like image processing, file I/O
- Examples: Network requests, database operations, intensive calculations
// Background operations
import { Image } from "expo-image"
// Image loading happens on background thread
;<Image source={{ uri: "https://example.com/large-image.jpg" }} contentFit="cover" />
Component Lifecycle in Mobile Context
React Native components follow React lifecycle patterns with mobile-specific considerations:
App State Lifecycle
import { AppState } from "react-native"
export function useAppState() {
const [appState, setAppState] = useState(AppState.currentState)
useEffect(() => {
const subscription = AppState.addEventListener("change", (nextAppState) => {
if (appState.match(/inactive|background/) && nextAppState === "active") {
console.log("App has come to the foreground!")
// Refresh data, restart timers, etc.
} else if (nextAppState.match(/inactive|background/)) {
console.log("App has gone to the background!")
// Save data, pause operations, etc.
}
setAppState(nextAppState)
})
return () => subscription?.remove()
}, [appState])
return appState
}
Screen Focus Lifecycle
import { useFocusEffect } from "@react-navigation/native"
export function ProfileScreen() {
useFocusEffect(
useCallback(() => {
// Screen is focused
console.log("Screen focused")
// Cleanup when screen loses focus
return () => {
console.log("Screen unfocused")
}
}, [])
)
return <View>{/* Screen content */}</View>
}
Memory Management
Mobile devices have limited memory, making efficient memory management crucial:
Component Cleanup
function DataScreen() {
const [data, setData] = useState([])
const intervalRef = useRef<NodeJS.Timeout>()
useEffect(() => {
// Set up interval
intervalRef.current = setInterval(() => {
fetchLatestData().then(setData)
}, 5000)
// Critical: cleanup on unmount
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current)
}
}
}, [])
return <DataList data={data} />
}
Event Listener Cleanup
function LocationScreen() {
useEffect(() => {
const subscription = Location.watchPositionAsync({ accuracy: Location.Accuracy.High }, (location) => {
setCurrentLocation(location)
})
return () => {
subscription.then((sub) => sub.remove())
}
}, [])
}
Platform Differences
React Native abstracts many platform differences, but understanding them helps with optimization:
iOS Specifics
import { Platform } from "react-native"
const styles = StyleSheet.create({
container: {
flex: 1,
// iOS safe area handling
paddingTop: Platform.OS === "ios" ? 44 : 0,
},
shadow: Platform.select({
ios: {
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 3.84,
},
android: {
elevation: 5,
},
}),
})
Android Specifics
import { BackHandler } from "react-native"
function useBackHandler(handler: () => boolean) {
useEffect(() => {
if (Platform.OS === "android") {
const subscription = BackHandler.addEventListener("hardwareBackPress", handler)
return () => subscription.remove()
}
}, [handler])
}
Performance Considerations
Avoiding Bridge Bottlenecks
// ❌ Frequent bridge crossings
setInterval(() => {
// This crosses the bridge every 16ms!
Animated.timing(animatedValue, {
toValue: 1,
duration: 16,
useNativeDriver: false, // Uses bridge
}).start()
}, 16)
// ✅ Native animations
Animated.timing(animatedValue, {
toValue: 1,
duration: 1000,
useNativeDriver: true, // Runs on native thread
}).start()
Optimizing List Performance
// ✅ Optimized list rendering
function OptimizedList({ data }: { data: Item[] }) {
const renderItem = useCallback(({ item }: { item: Item }) => <ListItem item={item} />, [])
const keyExtractor = useCallback((item: Item) => item.id, [])
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={keyExtractor}
removeClippedSubviews={true}
maxToRenderPerBatch={10}
windowSize={10}
initialNumToRender={10}
getItemLayout={(data, index) => ({
length: 60,
offset: 60 * index,
index,
})}
/>
)
}
Error Boundaries for Mobile
Error boundaries are crucial for mobile apps to prevent crashes:
import { ErrorBoundary } from "react-error-boundary"
function ErrorFallback({ error, resetErrorBoundary }: any) {
return (
<View style={styles.errorContainer}>
<Text style={styles.errorTitle}>Something went wrong</Text>
<Text style={styles.errorMessage}>{error.message}</Text>
<Pressable style={styles.retryButton} onPress={resetErrorBoundary}>
<Text style={styles.retryText}>Try again</Text>
</Pressable>
</View>
)
}
function App() {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onError={(error, errorInfo) => {
// Log to crash reporting service
console.error("App Error:", error, errorInfo)
}}
>
<MainNavigator />
</ErrorBoundary>
)
}
Development vs Production Differences
Understanding the differences between development and production builds:
Development Mode
- JavaScript bundle runs in debug mode
- Hot reloading and live reload enabled
- Source maps available for debugging
- Performance is slower due to development overhead
Production Mode
- Optimized JavaScript bundle
- No debugging tools
- Better performance and smaller bundle size
- Crash reporting and analytics enabled
import { __DEV__ } from "react-native"
function setupApp() {
if (__DEV__) {
// Development-only code
import("./DevMenu").then(({ enableDevMenu }) => {
enableDevMenu()
})
} else {
// Production-only code
crashlytics().log("App started in production mode")
}
}
Next Steps
Now that you understand the core concepts, you can move on to:
- Project Structure - Learn how to organize your React Native project
- Development Fundamentals - Master components and styling
- Performance Optimization - Advanced performance techniques