本文へ移動
Zomer Gregorio

Zomer Gregorio

ソフトウェアエンジニア

履歴書
言語
← ブログ

React Native: Optimizing Bridge-less Architecture and New Architecture Performance

· React Native · Mobile Performance · TypeScript · Architecture

Analyze the performance implications of React Native's New Architecture, focusing on JSI, TurboModules, and Fabric to eliminate bridge serialization bottlenecks.

更新情報を受け取る

新しい記事を公開したときに短いお知らせを送ります。メールまたはブラウザの購読情報は通知配信のためだけに保存され、いつでも解除できます。アカウントや追跡用プロフィールは不要です。

通知を開始する前に確認メールでの承認が必要です。

Introduction to the New Architecture

For years, React Native applications suffered from a fundamental architectural limitation: the asynchronous, serialized bridge. Every message passing between the JavaScript runtime and the native platform had to be serialized to JSON, queued, and sent across a serialized boundary. This caused frame drops, input latency, and unpredictable garbage collection pauses.

The introduction of React Native's New Architecture eliminates this legacy bridge entirely. By leveraging the JavaScript Interface (JSI), TurboModules, and Fabric, developers can now build applications that execute synchronous operations, share memory references between C++ and JavaScript, and provide fluid, native-grade gesture handling.

Understanding how to migrate to and optimize for this bridge-less environment requires a deep dive into JSI bindings, memory management across runtime boundaries, and the lifecycle of native components under the Fabric renderer.

The Role of JSI (JavaScript Interface)

The JavaScript Interface is a lightweight, template-based C++ API that allows JavaScript to hold references to C++ objects and invoke methods directly. Unlike the old bridge, which required asynchronous queuing and string serialization, JSI enables synchronous execution.

Memory Safety and Threading

With JSI, JavaScript references are no longer isolated from native memory spaces. When exposing C++ objects to JavaScript, managing object lifetimes becomes critical. If a C++ object backing a native module is deallocated while JavaScript still holds a JSI HostObject reference, segmentation faults will occur.

To prevent threading deadlocks, the New Architecture enforces strict runtime threading models. JavaScript executes on its dedicated JS thread, while native UI operations are scheduled on the main thread via EventEmitters and concurrent state updates. Synchronous JSI calls executed from the JS thread into native modules must avoid heavy computational blocking or direct UI mutations.

import { TurboModule, TurboModuleRegistry } from 'react-native';
 
export interface Spec extends TurboModule {
  getSecureDeviceToken(): Promise<string>;
  computeHashSync(data: string): string;
}
 
export default TurboModuleRegistry.getEnforcing<Spec>('NativeCryptoModule');

TurboModules: Lazy Loading and Codegen

Legacy native modules required initialization at application startup, inflating time-to-interactive (TTI) metrics even if the module was never invoked by the user. TurboModules solve this by introducing lazy loading.

Codegen Integration

React Native uses a static type-checking build step called Codegen to enforce strict contracts between TypeScript specifications and native C++ implementations. Codegen parses your TypeScript definitions at build time to generate native interface boilerplate for iOS (Objective-C++/Swift) and Android (C++/Java).

  1. Define your module interface in TypeScript using strict types.
  2. Run the Codegen script during your Gradle or Xcode build phases.
  3. Implement the generated native interface in C++ or your preferred platform language.

This eliminates runtime type-mismatch crashes and ensures that invalid payloads fail at compile time rather than in production.

Fabric Renderer and Concurrent React

Fabric is the new concurrent-capable UI rendering engine. It decouples the React shadow tree from the native platform view hierarchy, allowing React to prepare layout measurements off the main thread.

Host View Recycling and State Management

In the old architecture, layout updates required traversing the bridge and mutating native views individually. Fabric introduces a shared C++ shadow node tree. When state changes occur:

  • React computes layout updates concurrently without blocking the UI thread.
  • Mutated nodes are committed atomically to the native view hierarchy.
  • Native views are recycled efficiently via component recycling pools, reducing memory allocation churn.

Handling Gestures and Animations

Because Fabric integrates directly with gesture systems like Reanimated, animations run entirely on the UI thread without crossing the JSI boundary per frame. Avoid driving layout animations via React state updates. Instead, use worklets that mutate shared values directly via JSI host functions.

Migration Strategies and Fallbacks

Migrating a large, legacy React Native codebase to the New Architecture requires a phased approach. Not all third-party libraries have been upgraded to support TurboModules and Fabric.

Interop Layers

React Native provides backward-compatibility interop layers for both legacy native modules and legacy view managers. While these layers allow you to adopt the New Architecture incrementally, they introduce performance overhead.

  • Audit your dependency tree for legacy bridge usages.
  • Fork or patch unmaintained libraries that rely heavily on RCTBridgeModule.
  • Test your application with the New Architecture enabled strictly in staging builds before production rollouts.

Performance Profiling and Debugging

Profiling a bridge-less application requires shifting focus from bridge traffic monitors to native memory profilers and CPU flame graphs.

Common Bottlenecks

  • Excessive JSI boundary crossings: Making thousands of synchronous JSI calls in a tight loop will block the JavaScript thread and drop frames.
  • Memory leaks in HostObjects: Failing to release native resource handles linked to JSI objects will cause native memory bloat.
  • Thread contention: Executing blocking I/O operations inside synchronous native module methods.

Use Xcode Instruments and Android Studio Profiler to inspect memory allocations, C++ heap growth, and thread contention metrics. Ensure your production builds have debugging symbols properly managed to trace native crashes accurately.