Embracing the Future: A Developer's First Look at React Native's New Architecture & Migration

February 5, 2024 (1y ago)

The React Native landscape is undergoing its most significant transformation yet with the rollout of the New Architecture. This isn't just an incremental update; it's a fundamental reimagining of how React Native operates under the hood, promising a future of more performant, reliable, and robust mobile applications. Having navigated the migration process for several projects, I'm here to share my first impressions, practical considerations, and a roadmap for developers embarking on this exciting journey.

Decoding the New Architecture: What's Changed and Why It Matters

At its heart, the New Architecture aims to address historical performance bottlenecks and improve the overall developer experience. Let's break down the key players:

1. The JavaScript Interface (JSI) - The Unsung Hero

While not always in the spotlight, JSI is the true foundation. It's a lightweight, general-purpose C++ API that allows JavaScript to hold references to C++ host objects and invoke methods on them directly.

2. Fabric Renderer - The New UI Engine

Fabric is React Native's new rendering system, built on top of JSI.

3. TurboModules - Native Modules, Supercharged

TurboModules are the next evolution of Native Modules, also leveraging JSI.

4. Codegen - The Scaffolding Automator

Codegen plays a crucial behind-the-scenes role by automatically generating the C++ "glue" code required for Fabric components and TurboModules to communicate with JavaScript in a type-safe manner. This reduces boilerplate and potential for human error.

The Collective Impact: This architectural overhaul aims for:

Your Migration Blueprint: A Phased Approach

Migrating to the New Architecture isn't a flip of a switch; it requires careful planning and execution. Here's a suggested phased strategy:

Phase 1: Preparation & Assessment ("Know Before You Go")

  1. Upgrade React Native: Ensure you're on a version that supports the New Architecture (generally 0.68+ for initial support, 0.70+ recommended for stability).
  2. Enable the New Architecture: This is the first practical step.
    • Android: In gradle.properties, set newArchEnabled=true. You might also need to adjust JVM arguments (e.g., org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=512m).
    • iOS: In your Podfile, modify the use_react_native! call:
      use_react_native!(
        :path => config[:reactNativePath],
        # Hermes is practically a prerequisite for the New Architecture
        :hermes_enabled => true, 
        :fabric_enabled => true, # Enable Fabric
        # :turbomodule_enabled => true # Often enabled by default with Fabric
      )
      Then run pod install.
  3. Audit Your Dependencies: This is CRITICAL.
    • Identify all third-party native modules your app uses.
    • Check their documentation or GitHub issues for New Architecture (Fabric/TurboModule) compatibility. Many popular libraries have already migrated or have beta support.
    • For incompatible libraries, you'll need to:
      • Find an alternative library that is compatible.
      • Fork the library and attempt to migrate it yourself (advanced).
      • Temporarily remove the functionality if it's non-critical.
      • Use the "Bridgeless Mode" or compatibility layers if available, though full migration is the goal.
    • Conceptual check:
      // This is a conceptual check, actual tools or manual review are needed.
      // You might use tools or scripts to list native modules.
      async function checkThirdPartyModuleCompatibility() {
        const knownIncompatible = ['some-old-library', 'another-unsupported-module'];
        const currentModules = Object.keys(require('react-native').NativeModules);
        
        const issues = currentModules.filter(mod => knownIncompatible.includes(mod));
        if (issues.length > 0) {
          console.warn("Potential New Architecture compatibility issues with:", issues);
        } else {
          console.log("Initial check suggests third-party modules might be compatible or have paths forward.");
        }
        return issues;
      }
  4. Address Deprecated APIs: Remove usage of deprecated React Native APIs that might not be compatible.
  5. Set Up a Test Environment: Ensure you can build and run your app on both platforms with the New Architecture enabled, even if some things are broken initially.

Phase 2: Migrating Your Native Modules to TurboModules

If you have custom native modules, they'll need to be updated.

  1. Understand the Spec: TurboModules require a JavaScript specification (written in TypeScript or Flow) that defines the module's interface. Codegen uses this spec.
  2. Update Native Code:
    • Android (Java/Kotlin): Your native module class needs to implement the generated interface.
    • iOS (Objective-C/Swift): Similar to Android, you'll conform to the generated protocol/interface.
    • C++ (Optional but Powerful): For cross-platform native modules, you can write the core logic in C++ and create thin platform-specific wrappers. This often involves a TurboModuleManagerDelegate.
    • Simplified C++ Example (Conceptual - requires more setup):
      // MyTurboModule.h (Spec defines this structure)
      // #pragma once // if using pragma
      // #ifndef MyTurboModule_h
      // #define MyTurboModule_h
       
      // #include <ReactCommon/TurboModule.h>
      // #include <string>
       
      // namespace facebook {
      // namespace react {
       
      // class JSI_EXPORT MyTurboModuleSpecJSI : public TurboModule {
      // protected:
      //   MyTurboModuleSpecJSI(std::shared_ptr<CallInvoker> jsInvoker);
      // public:
      //   virtual jsi::String greet(jsi::Runtime &rt, jsi::String name) = 0;
      // };
      // } // namespace react
      // } // namespace facebook
      // #endif /* MyTurboModule_h */
       
       
      // MyTurboModule.cpp (Implementation)
      // #include "MyTurboModule.h" // Assuming your spec generated header
      // #include <jsi/jsi.h>
       
      // namespace facebook {
      // namespace react {
       
      // MyTurboModule::MyTurboModule(std::shared_ptr<CallInvoker> jsInvoker)
      //     : MyTurboModuleSpecJSI(std::move(jsInvoker)) {} // Corrected base class
       
      // jsi::String MyTurboModule::greet(jsi::Runtime &rt, jsi::String name) {
      //   std::string result = "Hello, " + name.utf8(rt) + "! From TurboModule!";
      //   return jsi::String::createFromUtf8(rt, result);
      // }
       
      // // Factory function to create the module - called by TurboModuleManagerDelegate
      // std::shared_ptr<TurboModule> MyTurboModule_ModuleProvider(const std::string moduleName, const JavaTurboModule::InitParams &params) {
      //    // This is more for Android. iOS has a different registration.
      //    // For a shared C++ module, delegate would call constructor.
      //    return std::make_shared<MyTurboModule>(params.jsInvoker);
      // }
      // } // namespace react
      // } // namespace facebook
      Note: The C++ part is complex and requires deep integration with your build system and the React Native infrastructure. Start with migrating platform-specific Java/Obj-C modules first if you're new to this.

Phase 3: Migrating UI Components to Fabric-Compatible Components

Custom native UI components also need updating to be Fabric-aware.

  1. JavaScript Spec: Similar to TurboModules, define the props and events for your native component in a JavaScript spec file.
  2. Update Native View Managers:
    • Your native view manager code needs to be updated to work with Fabric's rendering system. This often involves changes to how props are handled and how views are created and managed.
    • Codegen will generate interfaces that your native view manager must implement.
    • Conceptual Fabric Component Invocation (JS side remains similar for basic components):
      // MyFabricComponent.js - Assuming 'RNMyFabricComponent' is codegen'd
      // import { requireNativeComponent } from 'react-native';
      // const RNMyFabricComponent = requireNativeComponent('MyFabricComponent');
       
      // // If using ViewConfig and types from codegen:
      // import type { ViewProps } from 'react-native/Libraries/Components/View/ViewPropTypes';
      // import type { HostComponent } from 'react-native';
      // interface NativeProps extends ViewProps {
      //   customProp?: string;
      //   // Define other props from your spec
      // }
      // export default requireNativeComponent<NativeProps>('MyFabricComponent') as HostComponent<NativeProps>;
       
      // // Usage:
      // // <MyFabricComponent customProp="Hello Fabric!" style={{ width: 100, height: 100 }} />
      For components that are already pure JavaScript (like <View>, <Text>), they become Fabric-compatible automatically when Fabric is enabled. This phase is primarily for custom native UI components.

Phase 4: Testing and Iteration

  1. Thorough Testing: Test all aspects of your application on both iOS and Android, focusing on areas that use migrated native modules or UI components.
  2. Performance Profiling: Use Flipper, Xcode Instruments, and Android Studio Profiler to identify any new bottlenecks or regressions.
  3. Iterate: Address any issues found during testing. Migration is often an iterative process.

The Payoff: Tangible Performance Improvements & Benefits

So, what are the real-world results? While exact numbers vary by app complexity and how well optimizations are implemented, here's what we've generally observed:

Before Migration (Old Architecture - Illustrative Metrics)

After Migration (New Architecture - Illustrative Metrics)

Practical Considerations & Challenges: The Nitty-Gritty

Looking Forward: The Future is Bright (and Fast!)

The New Architecture is undeniably the future of React Native. While the migration path requires effort and careful planning, the benefits in terms of performance, stability, and developer experience are substantial. It lays the groundwork for even more exciting advancements in the React Native ecosystem.

For most applications, especially those aiming for top-tier performance and long-term maintainability, starting the migration journey or building new apps with the New Architecture enabled from the get-go is a strategic imperative.


Are you migrating to the New Architecture? What are your biggest challenges or exciting discoveries? Share your thoughts in the comments below!