React Nativeはクロスプラットフォーム開発の主力フレームワークとして広く採用されていますが、パフォーマンス面での課題は長年の懸念事項でしました。特に、JavaScriptとネイティブ間の通信がブリッジを経由する旧アーキテクチャでは、大量のデータ転送やアニメーション処理でボトルネックが発生しやすい状況でしました。
しかし、React Native 0.68から段階的に導入されているNew Architecture(新アーキテクチャ)により、これらの課題に根本的な改善がもたらされています。筆者が担当するアプリでも新アーキテクチャへの移行を完了し、体感できるレベルのパフォーマンス改善を実現しましました。本記事では、その実践知見をお伝えします。
New Architectureの中核となるのがJSIです。従来のブリッジはJSONシリアライゼーションを介してJavaScriptとネイティブ間で通信していましたが、JSIはC++レイヤーを通じて直接的な関数呼び出しを実現します。
// 従来のブリッジ方式(概念的な流れ)
// JS → JSON.stringify → Bridge → JSON.parse → Native
// JSIを使った直接呼び出し
import { multiply } from 'react-native-quick-crypto';
// C++を通じてネイティブ関数を直接呼び出し
const result = multiply(3, 7); // ブリッジ不要、同期的に実行可能
この変更により、データのシリアライゼーション/デシリアライゼーションのオーバーヘッドが排除され、特に高頻度な呼び出しで劇的な改善が見られます。
FabricはReact Nativeの新しいレンダリングシステムです。従来はUI更新がブリッジ経由で非同期に行われていたため、スクロール中のUI更新やジェスチャーレスポンスに遅延が生じることがありましました。Fabricではレンダリングパイプラインが再設計され、同期的なレイアウト計算と優先度ベースのレンダリングが可能になります。
// Fabricでは同期的なレイアウト計算が可能
import { useLayoutEffect } from 'react';
import { measure } from 'react-native-reanimated';
function AdaptiveComponent() {
const ref = useAnimatedRef();
useLayoutEffect(() => {
// 同期的にレイアウト情報を取得できる
const measurement = measure(ref);
if (measurement) {
console.log('Width:', measurement.width);
console.log('Height:', measurement.height);
}
}, []);
return {/* content */};
}
Turbo Modulesは、従来のNative Modulesの後継です。遅延ロード(Lazy Loading)に対応しており、アプリ起動時に全モジュールを初期化する必要がなくなりましました。これにより、起動時間の短縮に直結します。
// TurboModule の定義例(TypeScript spec)
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
getDeviceName(): string;
getBatteryLevel(): Promise<number>;
}
export default TurboModuleRegistry.getEnforcing<Spec>(
'DeviceInfo'
);
Turbo Modulesでは型定義がCodegenによりネイティブコードに自動変換されるため、型安全性が保証されます。従来のNative Modulesで発生しがちだった型不一致によるランタイムエラーを防止できます。
大量データのリスト表示は、React Nativeで最もパフォーマンス課題が発生しやすい領域です。FlashListの導入は最初に検討すべき施策です。
import { FlashList } from '@shopify/flash-list';
function OptimizedList({ data }) {
const renderItem = useCallback(({ item }) => (
<ListItem title={item.title} />
), []);
return (
<FlashList
data={data}
renderItem={renderItem}
estimatedItemSize={80}
keyExtractor={(item) => item.id}
/>
);
}
FlashListはShopifyが開発したFlatListの高性能な代替コンポーネントです。セルのリサイクル機構が優れており、筆者のテストでは1万件のデータ表示でFlatList比約5倍のフレームレート改善を確認しましました。
React Native Reanimatedを活用し、アニメーション処理をUIスレッドで実行することが重要です。JSスレッドでのアニメーションは、ガベージコレクションなどの影響でジャンクが発生しやすくなります。
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
} from 'react-native-reanimated';
function AnimatedCard() {
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
const onPressIn = () => {
scale.value = withSpring(0.95);
};
const onPressOut = () => {
scale.value = withSpring(1);
};
return (
<Animated.View style={animatedStyle}>
<Pressable onPressIn={onPressIn} onPressOut={onPressOut}>
{/* カード内容 */}
</Pressable>
</Animated.View>
);
}
New Architectureへの移行では、以下の点に注意が必要です。
React NativeのNew Architectureは、長年の課題でしたパフォーマンス問題に対する本質的な解決策です。JSI、Fabric、Turbo Modulesの三つの柱により、ネイティブアプリに迫るパフォーマンスを実現できます。移行にはコストが伴いますが、ユーザー体験の向上を考えれば十分に価値のある投資です。