Loading post...
Deep dive into React Native's threading model, the new architecture, and battle-tested techniques for buttery smooth animations
rayanstudio
60 FPS isn't optional. It's the minimum standard for a professional mobile app. Here's how to achieve it.
Mobile App Performance
React Native uses three threads:
┌─────────────────┐
│ JS Thread │ ← Your React code runs here
├─────────────────┤
│ UI Thread │ ← Native UI rendering
├─────────────────┤
│ Shadow Thread │ ← Layout calculations
└─────────────────┘The golden rule: Never block the JS thread.
// Every state update crosses the bridge
setState({ count: count + 1 })
// ↓
// Serialization
// ↓
// Bridge crossing (slow!)
// ↓
// Deserialization
// ↓
// UI updateFor high-frequency updates (scrolling, gestures), this creates:
If this exceeds 16.67ms, you drop frames.
// Direct synchronous access to native
import { Image } from 'react-native'
// Before (Bridge): Async, serialized
const size = await Image.getSize(uri)
// After (JSI): Sync, direct
const size = Image.getSizeSync(uri)// C++ host objects shared between JS and Native
// No serialization overhead!
function MyComponent() {
return (
{/* Direct manipulation, no bridge */}
Hello
);
}Architecture Diagram
import Animated, {
useAnimatedStyle,
useSharedValue,
withSpring,
} from 'react-native-reanimated';
function AnimatedBox() {
const offset = useSharedValue(0);
const animatedStyles = useAnimatedStyle(() => ({
transform: [{ translateX: offset.value }],
}));
return (
<Pressable
onPress={() => {
// Runs on UI thread!
offset.value = withSpring(offset.value + 100);
}}
>
Move Me
);
}Why this matters: The animation runs entirely on the UI thread. JS thread can be blocked, animation still runs at 60 FPS.
import { FlashList } from '@shopify/flash-list';
function OptimizedList({ data }) {
const renderItem = useCallback(({ item }) => (
), []);
return (
<FlashList
data={data}
renderItem={renderItem}
estimatedItemSize={100}
// Key optimizations
removeClippedSubviews={true}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={50}
initialNumToRender={10}
windowSize={5}
// Recycling for massive lists
recyclerConfig={{
type: 'uniform',
averageHeight: 100,
}}
/>
);
}
const MemoizedItem = memo(({ item }) => (
{item.title}
));Performance metrics:
import FastImage from 'react-native-fast-image';
function OptimizedImage({ uri }) {
return (
);
}Gains:
import { startProfiler, stopProfiler } from '@react-native-community/cli-tools'
function ProfiledComponent() {
useEffect(() => {
const id = startProfiler('ComponentMount')
return () => {
stopProfiler(id)
}
}, [])
return
}Look for:
import { Image } from 'react-native'
class ImageManager {
private cache = new Map()
async load(uri: string): Promise {
if (this.cache.has(uri)) {
return this.cache.get(uri)!
}
const path = await Image.prefetch(uri)
// Limit cache size
if (this.cache.size > 50) {
const firstKey = this.cache.keys().next().value
this.cache.delete(firstKey)
}
this.cache.set(uri, path)
return path
}
clear() {
this.cache.clear()
}
}60 FPS isn't magic. It's engineering.