DEV Community

Cover image for React Native: Uma análise da nova arquitetura de Turbo Native Modules
Wesley Rodrigues
Wesley Rodrigues

Posted on

React Native: Uma análise da nova arquitetura de Turbo Native Modules

A partir da versão 0.76 do react native a nova arquitetura foi habilitada por padrão para aplicativos a nível de produção.Essa nova arquitetura agora permite a criação de modulos nativos

A nova arquitetura introduz TurboModules + Codegen, que mudam completamente a forma como JavaScript conversa com código nativo.

Neste artigo, você vai aprender sobre:

  • Problemas da arquitetura antiga
  • Funcionamento da nova arquitetura
  • Um exemplo completo (Android + TypeScript)
  • Boas práticas no uso de Turbo Modules

Problemas da arquitetura antiga

Na arquitetura antiga o fluxo de comunicação dos módulos nativos era baseado na “Bridge”. Como o próprio nome remete seria a ponte entre o JavaScript que é executado na Thread até a entrega de elementos nativos com o objetivo de gerar interfaces interativas com usabilidade e experiencia de desenvolvimento o máximo possível semelhantes ao desenvolvimento nativo utilizando uma unica code-base para isso.

Para facilitar seu entendimento pense os dois sistemas são dois estrangeiros um japonês e outro espanhol, que precisam conversar entre si e para isso buscam um idioma comum entre os dois para se comunicarem e usam o inglês por exemplo, nesse caso da arquitetura antiga do react native o “idioma” em comum entre seria o JSON.

Fluxo da arquitetura antiga: JS Bundle, Bridge assíncrona, JSON e UI nativa

Isso funcionava bem quando os aplicativos eram mais simples, mas a medida que os aplicativos começaram a crescer com mais funcionalidades, aumento de complexidade, evolução do react começou a surgir alguns problemas nessa arquitetura:

1) A Bridge é apenas async

Por ser somente assíncrona, ter serialização JSON em cada chamada, lentidão com o aumento de grandes transferências de dados e além da possibilidade de ocorrer um JSON overhead em determinados casos.

2) Renderização de UI

O motor de renderização usado era o UIManger (Native) o que fazia as atualizações de interfaces causar lag além da latência alta em determinadas animações.

3) Difícil suporte as novas features no React

Renderização simultânea, Suspense e Transitions e possíveis novos recursos do React no futuro eram bloqueados de serem suportados de forma limpa por usar essa arquitetura. Era necessário uma estrutura mais forte para estruturar esse novos recursos, nessa situação entra a necessidade da criação da nova arquitetura do react native.

4) Módulos nativos mais pesados

  • Todos os módulos carregados no startup da aplicação.
  • Comunicação lenta e sem type safety real
  • Contratos verificados em runtime
  • Sem suporte C++ cross-platform
  • Cada chamada passa por uma camada intermediária

Funcionamento da nova arquitetura

Na nova arquitetura funciona dessa forma;

A nova arquitetura é construída como parte de uma reformulação completa nos módulos e nas camadas que fazem a ligação entre o código Javascript e o código nativo. Essa nova arquitetura têm como diferenciais quatro partes principais:

  • O novo sistema de módulos nativos
  • O novo renderizador
  • O Event Loop
  • Remoção da Bridge

Comparação entre a arquitetura antiga (Bridge assíncrona) e a nova (comunicação direta via JSI)

Essa nova arquitetura traz como principais benefícios a questão performática, menor tamanho fazendo os módulos carregarem sob demanda, uma melhor experiencia de UX, e uma melhor arquitetura preparada para escalar conforme sua aplicação cresce.

O principio de funcionamento da nova arquitetura é basicamente assim:

1) JSI (JavaScript Interface) É uma camada em C++ que faz a comunicação diretamente o runtime do JavaScript (Hermes, JSC) com o código nativo. essa comunicação é feita direta na memória sem serialização (JSON) e sem filas de mensagens. Tendo como resultado essa comunicação sendo síncrona ou assíncrona e feita de forma muito mais rápida que antes

2) Fabric Um novo motor de renderização que substitui o antigo UIManager. Ele formado por três conceitos:

Shadow Tree (UI virtual) → Diff (Calculo de mudanças)→ Mount (aplica mudanças na UI nativa
). Com isso tivemos uma melhora considerável a consistência e o desempenho da UI

3) Turbo Modules substituto para os módulos nativos legados (NativeModules), permitindo uma comunicação mais rápida e segura entre o JavaScript e código nativo. E têm como características e diferenciais os seguintes aspectos:

  • Lazy loading: carrega só quando usado
  • Chamadas diretas via JSI
  • Baixa latência, sincronismo possível
  • Inicialização mais rápida
  • Menos consumo de memória

4) Codegen Ele gera automaticamente código nativo para TurboModules e os componentes, a partir de uma spec em TypeScript. Garantindo segurança de tipo de ponta a ponta e evitando erros manuais.

Fluxo do Codegen: spec TypeScript, geração de stubs nativos e implementação

Nova arquitetura do React Native: JSI, Fabric, Turbo Modules e Codegen

Como criar um módulo Turbo Native

Vou fornecer o guia passo a passo de como você pode para criar seu primeiro Turbo Module nativo com a nova arquitetura do do React Native. Nesse exemplo vou criar de modo simples e genérico um modulo nativo, no qual com você pode usar esses conceitos em qualquer outra funcionalidade nativa.

Pré-requisitos

  • React Native 0.76+ (New Architecture habilitada por padrão)
  • Xcode 15+
  • Android Studio com Kotlin
  • Node.js 18+

1: Estrutura das pastas

Inicie um projeto react native

A estrutural final das pastas para a criação de um módulo nativo vai ficar assim:

project-root/
├── src/
│   ├── native/
│   │   ├── NativeBatteryToast.ts       # Codegen Spec (contrato TS ↔️ Nativo)
│   │   └── BatteryToast.ts             # Wrapper tipado para consumo em componentes
│   └── screens/
│       └── battery.tsx                  # Tela demo
├── android/
│   └── app/src/main/java/com/myprojectname/
│       ├── MainApplication.kt          # Registro do package
│       └── batterytoast/
│           ├── BatteryToastModule.kt    # Implementação nativa Android
│           └── BatteryToastPackage.kt   # Registro do módulo no RN
└── ios/
    └── MyProjectName/
        └── BatteryToast/
            └── BatteryToastModule.mm    # Implementação nativa iOS (Objective-C++)
Enter fullscreen mode Exit fullscreen mode

2: Criar o Codegen Spec (TypeScript)

O Codegen Spec é o contrato entre JavaScript e o código nativo. Ele define quais métodos existem, seus parâmetros e seus retornos.

Regras do Codegen Spec:

  • O nome do arquivo Deve começar com Native (ex: NativeMeuModulo.ts)
  • A interface Deve se chamar Spec e estender TurboModule
  • Tipos suportados: string, number, boolean, Object (com shape definida), Array, Promise<T>
  • O nome passado para getEnforcing() é o identificador do módulo nativo

Crie o arquivo src/native/NativeCustomDeviceInfo.ts:

import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  // Métodos síncronos — retornam imediatamente via JSI
  getDeviceModel(): string;
  getSystemName(): string;
  getSystemVersion(): string;

  // Métodos assíncronos — retornam Promise
  getDeviceInfo(): Promise<{
    model: string;
    systemName: string;
    systemVersion: string;
    appVersion: string;
    buildNumber: string;
  }>;

  // Método com parâmetros
  isFeatureSupported(featureName: string): Promise<boolean>;
  // Obrigatórios para módulos com eventos
  addListener(eventName: string): void;
  removeListeners(count: number): void;
}

export default TurboModuleRegistry.getEnforcing<Spec>('CustomDeviceInfo');
Enter fullscreen mode Exit fullscreen mode

3: Configurar o Codegen no package.json

Adicione a seção codegenConfig ao package.json:

{
  "codegenConfig": {
    "name": "AppSpecs",
    "type": "modules",
    "jsSrcsDir": "src/native",
    "android": {
      "javaPackageName": "com.seuapp"
    },
    "ios": {
      "modulesProvider": {
           "CustomDeviceInfo": "DeviceInfoModule"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
  • name: Nome do módulo gerado (usado nos imports nativos)
  • jsSrcsDir: Diretório onde estão os specs
  • modulesProvider (iOS): Mapeia o nome do módulo JS para a classe nativa

4: Implementação Android (Kotlin)

4.1 Criar o Módulo

Crie android/app/src/main/java/com/seuapp/deviceinfo/DeviceInfoModule.kt:

package com.seuapp

import android.os.Build
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.WritableNativeMap
import com.facebook.react.module.annotations.ReactModule
import com.nativemodule.NativeCustomDeviceInfoSpec

@ReactModule(name = DeviceInfoModule.NAME)
class DeviceInfoModule(reactContext: ReactApplicationContext) :
    NativeCustomDeviceInfoSpec(reactContext) {

    companion object {
        const val NAME = "CustomDeviceInfo"
    }

    override fun getName(): String = NAME

    override fun getDeviceModel(): String {
        return "${Build.MANUFACTURER} ${Build.MODEL}"
    }

    override fun getSystemName(): String {
        return "Android"
    }

    override fun getSystemVersion(): String {
        return Build.VERSION.RELEASE
    }

    override fun getDeviceInfo(promise: Promise) {
        try {
            val packageInfo = reactApplicationContext.packageManager
                .getPackageInfo(reactApplicationContext.packageName, 0)

            val result = WritableNativeMap().apply {
                putString("model", getDeviceModel())
                putString("systemName", getSystemName())
                putString("systemVersion", getSystemVersion())
                putString("appVersion", packageInfo.versionName ?: "unknown")
                putString("buildNumber", packageInfo.versionCode.toString())
            }
            promise.resolve(result)
        } catch (e: Exception) {
            promise.reject("ERROR", "Failed to get device info: ${e.message}")
        }
    }

    override fun isFeatureSupported(featureName: String, promise: Promise) {
        val supported = when (featureName) {
            "bluetooth" -> reactApplicationContext.packageManager
                .hasSystemFeature("android.hardware.bluetooth")
            "camera" -> reactApplicationContext.packageManager
                .hasSystemFeature("android.hardware.camera")
            "nfc" -> reactApplicationContext.packageManager
                .hasSystemFeature("android.hardware.nfc")
            else -> false
        }
        promise.resolve(supported)
    }

    override fun addListener(eventName: String) {
        // No-op: required by EventEmitter interface
    }

    override fun removeListeners(count: Double) {
        // No-op: required by EventEmitter interface
    }
}
Enter fullscreen mode Exit fullscreen mode

4.2 Criar o Package

package com.seuapp

import com.facebook.react.TurboReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider

class DeviceInfoPackage : TurboReactPackage() {

    override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
        return when (name) {
            DeviceInfoModule.NAME -> DeviceInfoModule(reactContext)
            else -> null
        }
    }

    override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
        return ReactModuleInfoProvider {
            mapOf(
                DeviceInfoModule.NAME to ReactModuleInfo(
                    DeviceInfoModule.NAME,
                    DeviceInfoModule.NAME,
                    false, // canOverrideExistingModule
                    false, // needsEagerInit
                    false, // isCxxModule
                    true   // isTurboModule
                )
            )
        }
    }
override fun getPackages(): List<ReactPackage> =
    PackageList(this).packages.apply {
        add(DeviceInfoPackage())
    }
Enter fullscreen mode Exit fullscreen mode

4.3 Registrar no MainApplication.kt

override fun getPackages(): List<ReactPackage> =
    PackageList(this).packages.apply {
        add(MeuModuloPackage())
    }
Enter fullscreen mode Exit fullscreen mode

5: Implementação iOS

5.1 Criar o Módulo

Crie ios/SeuApp/DeviceInfo/DeviceInfoModule.mm:

#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
#import <ReactCommon/RCTTurboModule.h>
#import <ReactCodegen/AppSpecs/AppSpecs.h>
#import <UIKit/UIKit.h>
#import <sys/utsname.h>

#ifdef __cplusplus
using namespace facebook::react;
#endif

@interface DeviceInfoModule : RCTEventEmitter <NativeCustomDeviceInfoSpec>
@end

@implementation DeviceInfoModule

RCT_EXPORT_MODULE(CustomDeviceInfo)

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
  return std::make_shared<facebook::react::NativeCustomDeviceInfoSpecJSI>(params);
}

- (NSArray<NSString *> *)supportedEvents {
  return @[@"onDeviceInfoChange"];
}

+ (BOOL)requiresMainQueueSetup {
  return NO;
}

// MARK: - Métodos Síncronos

- (NSString *)getDeviceModel {
  struct utsname systemInfo;
  uname(&systemInfo);
  NSString *deviceModel = [NSString stringWithCString:systemInfo.machine
                                             encoding:NSUTF8StringEncoding];

  // Mapeia identificadores técnicos para nomes amigáveis
  NSDictionary *deviceMap = @{
    @"iPhone14,2": @"iPhone 13 Pro",
    @"iPhone14,3": @"iPhone 13 Pro Max",
    @"iPhone15,2": @"iPhone 14 Pro",
    @"iPhone15,3": @"iPhone 14 Pro Max",
    // Adicione mais mapeamentos conforme necessário
  };

  return deviceMap[deviceModel] ?: deviceModel;
}

- (NSString *)getSystemName {
  return [[UIDevice currentDevice] systemName];
}

- (NSString *)getSystemVersion {
  return [[UIDevice currentDevice] systemVersion];
}

// MARK: - Métodos Assíncronos

RCT_EXPORT_METHOD(getDeviceInfo:(RCTPromiseResolveBlock)resolve
                  reject:(RCTPromiseRejectBlock)reject) {
  NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
  NSString *appVersion = infoDictionary[@"CFBundleShortVersionString"] ?: @"unknown";
  NSString *buildNumber = infoDictionary[@"CFBundleVersion"] ?: @"unknown";

  resolve(@{
    @"model": [self getDeviceModel],
    @"systemName": [self getSystemName],
    @"systemVersion": [self getSystemVersion],
    @"appVersion": appVersion,
    @"buildNumber": buildNumber
  });
}

RCT_EXPORT_METHOD(isFeatureSupported:(NSString *)featureName
                  resolve:(RCTPromiseResolveBlock)resolve
                  reject:(RCTPromiseRejectBlock)reject) {
  BOOL supported = NO;

  if ([featureName isEqualToString:@"bluetooth"]) {
    // Bluetooth sempre disponível no iOS moderno
    supported = YES;
  } else if ([featureName isEqualToString:@"camera"]) {
    supported = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera];
  } else if ([featureName isEqualToString:@"nfc"]) {
    // NFC disponível a partir do iPhone 7
    if (@available(iOS 11.0, *)) {
      supported = YES;
    }
  }

  resolve(@(supported));
}

RCT_EXPORT_METHOD(logDeviceInfo) {
  NSLog(@"[DeviceInfo] Device: %@, OS: %@ %@",
        [self getDeviceModel],
        [self getSystemName],
        [self getSystemVersion]);
}

// MARK: - Event Emitter (obrigatório)

RCT_EXPORT_METHOD(addListener:(NSString *)eventName) {
  // No-op
}

RCT_EXPORT_METHOD(removeListeners:(double)count) {
  // No-op
}

@end
Enter fullscreen mode Exit fullscreen mode

5.2 Adicionar ao Xcode

  • Diferente do Android (onde basta criar o arquivo na pasta correta), no iOS você precisa registrar o arquivo no projeto do Xcode para que ele seja compilado. Siga estes passos:
  • Abra o workspace no Xcode:

    open ios/SeuApp.xcworkspace
    

    Use .xcworkspace (não .xcodeproj) porque o CocoaPods gerencia dependências via workspace.

  • Crie a pasta do módulo:

    • No painel esquerdo (Project Navigator), clique com botão direito na pasta do app (ex: SeuApp/)
    • Selecione New Group e nomeie como DeviceInfo
  • Adicione o arquivo .mm:

    • Clique com botão direito na pasta DeviceInfo recém-criada
    • Selecione Add Files to "SeuApp"...
    • Navegue até ios/SeuApp/DeviceInfo/DeviceInfoModule.mm e selecione
    • Importante: Certifique-se de que o checkbox "Add to targets: SeuApp" está marcado
    • Clique em Add
  • Verifique a inclusão no target:

    • Selecione o projeto no navigator → aba Build Phases
    • Expanda Compile Sources e confirme que DeviceInfoModule.mm aparece na lista
    • Se não aparecer, clique em + e adicione manualmente
  • Header necessário para uname():

    • O #import <sys/utsname.h> já está incluído no sistema (faz parte do SDK do iOS)
    • Não precisa instalar nada extra — basta o import no topo do .mm

Dica: Se preferir criar o arquivo diretamente pelo Xcode (em vez de copiar), use File → New → File → selecione "Objective-C++ File" (.mm). O Xcode já adiciona ao target automaticamente nesse caso.

Alternativa via CLI: Se não quiser abrir o Xcode, você pode adicionar o arquivo ao project.pbxproj manualmente, mas isso é propenso a erros. O método via Xcode é mais seguro.

5.3 Por que Objective-C++ e não Swift?

O TurboModule usa JSI (JavaScript Interface), uma API C++. O método getTurboModule precisa retornar um std::shared_ptr<TurboModule> — isso é impossível em Swift puro.

Alternativas com Swift (avançado):

É possível separar a lógica em Swift e manter apenas o glue C++ no .mm, mas isso requer:

  • Um bridging header para expor headers do RN ao Swift
  • Forward declarations no .mm para a classe Swift
  • Configuração de DEFINES_MODULE = YES e SWIFT_OBJC_BRIDGING_HEADER
  • Cuidado com ordem de compilação e conflitos de definição

Para módulos simples, a complexidade não compensa. Para módulos grandes com muita lógica de negócio, pode valer a pena isolar o código Swift em classes auxiliares que o .mm chama.


6: Criar o Wrapper TypeScript

Crie src/native/DeviceInfo.ts — um wrapper ergonômico para consumo nos componentes:

import NativeDeviceInfo from "./NativeCustomDeviceInfo";

export interface DeviceInfoData {
  model: string;
  systemName: string;
  systemVersion: string;
  appVersion: string;
  buildNumber: string;
}

export const DeviceInfo = {
  // Métodos síncronos — executados via JSI, retornam imediatamente
  getDeviceModel(): string {
    return NativeDeviceInfo.getDeviceModel();
  },

  getSystemName(): string {
    return NativeDeviceInfo.getSystemName();
  },

  getSystemVersion(): string {
    return NativeDeviceInfo.getSystemVersion();
  },

  // Métodos assíncronos — retornam Promise
  getDeviceInfo(): Promise<DeviceInfoData> {
    return NativeDeviceInfo.getDeviceInfo();
  },

  isFeatureSupported(featureName: 'bluetooth' | 'camera' | 'nfc'): Promise<boolean> {
    return NativeDeviceInfo.isFeatureSupported(featureName);
  },

};
Enter fullscreen mode Exit fullscreen mode

7: Build e Teste

Nota sobre o Codegen: Não é necessário rodar o Codegen manualmente. Ele é invocado automaticamente:

  • iOS: O pod install executa o script do Codegen como parte da instalação dos pods (configurado no Podfile pelo React Native).
  • Android: O Gradle dispara o Codegen durante o build via task configurada pelo plugin do React Native.

Isso significa que os comandos abaixo já incluem a geração das interfaces nativas (NativeMeuModuloSpec). Caso precise regenerar manualmente (ex: após alterar o spec sem rebuildar), basta rodar pod install (iOS) ou limpar o build com cd android && ./gradlew clean (Android).

# iOS
cd ios && pod install && cd ..
npx react-native run-ios

# Android
npx react-native run-android
Enter fullscreen mode Exit fullscreen mode

8: Consumir Módulo Nativo

Com o wrapper criado no Passo 5, agora é possível consumir o módulo em qualquer componente. Aqui está um exemplo completo no App.tsx:

import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, ScrollView } from 'react-native';
import { DeviceInfo, DeviceInfoData } from './src/native/DeviceInfo';

export function DeviceScreen() {
  const [deviceInfo, setDeviceInfo] = useState<DeviceInfoData | null>(null);
  const [features, setFeatures] = useState({
    bluetooth: false,
    camera: false,
    nfc: false,
  });

  useEffect(() => {
    DeviceInfo.getDeviceInfo().then(setDeviceInfo);

    Promise.all([
      DeviceInfo.isFeatureSupported('bluetooth'),
      DeviceInfo.isFeatureSupported('camera'),
      DeviceInfo.isFeatureSupported('nfc'),
    ]).then(([bluetooth, camera, nfc]) => {
      setFeatures({ bluetooth, camera, nfc });
    });
  }, []);



  return (
    <ScrollView contentContainerStyle={styles.container}>
      <Text style={styles.title}>📱 Informações do Device>

      {/* Métodos síncronos via JSI */}
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>Informações Rápidas</Text>
        <Text style={styles.info}>Modelo: {DeviceInfo.getDeviceModel()}</Text>
        <Text style={styles.info}>Sistema: {DeviceInfo.getSystemName()}</Text>
        <Text style={styles.info}>Versão: {DeviceInfo.getSystemVersion()}</Text>
      </View>

      {/* Dados completos via Promise */}
      {deviceInfo && (
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>Informações Completas</Text>
          <Text style={styles.info}>Modelo: {deviceInfo.model}</Text>
          <Text style={styles.info}>
            OS: {deviceInfo.systemName} {deviceInfo.systemVersion}
          </Text>
          <Text style={styles.info}>App: v{deviceInfo.appVersion}</Text>
          <Text style={styles.info}>Build: {deviceInfo.buildNumber}</Text>
        </View>
      )}

      {/* Features suportadas */}
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>Features Disponíveis</Text>
        <Text style={styles.info}>
          Bluetooth: {features.bluetooth ? '✅' : '❌'}
        </Text>
        <Text style={styles.info}>
          Câmera: {features.camera ? '✅' : '❌'}
        </Text>
        <Text style={styles.info}>NFC: {features.nfc ? '✅' : '❌'}</Text>
      </View>


    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: {
    flexGrow: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
    textAlign: 'center',
  },
  section: {
    backgroundColor: 'white',
    padding: 16,
    borderRadius: 8,
    marginBottom: 16,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: '600',
    marginBottom: 12,
    color: '#333',
  },
  info: {
    fontSize: 16,
    marginBottom: 8,
    color: '#666',
  },
});

Enter fullscreen mode Exit fullscreen mode

Tela de exemplo consumindo o Turbo Module CustomDeviceInfo no emulador Android

O que este exemplo demonstra:

  • Chamadas síncronas via JSI — getDeviceModel(), getSystemName(), getSystemVersion() retornam imediatamente sem Promise
  • Chamada assíncrona — getDeviceInfo() retorna uma Promise com dados completos
  • Verificação de features — isFeatureSupported() consulta capacidades do hardware
  • UI responsiva — Usa useState + useEffect para atualizar a interface com dados nativos
1. Codegen Spec (TS)     → Define o contrato (NativeDeviceInfo.ts)
2. package.json config   → Configura o Codegen
3. `pod install` / build → Gera interfaces nativas (NativeDeviceInfoSpec)
4. Implementação nativa  → Kotlin (Android) + Objective-C++ (iOS)
5. Bridge .mm (iOS)      → Conecta C++ JSI ao código nativo
6. Wrapper TS            → API ergonômica para componentes (DeviceInfo.ts)
7. Build & run           → Testa no device/simulador
8. Consumo no App        → Usa o wrapper em componentes React
Enter fullscreen mode Exit fullscreen mode

Diferença entre métodos síncronos e assíncronos:

  • Síncronos (JSI): Executam diretamente na thread do JS via C++, sem overhead de serialização. Ideais para operações rápidas (leitura de propriedades).
  • Assíncronos (Promise): Executam na thread nativa e retornam via bridge. Necessários para operações que podem demorar (I/O, network, cálculos pesados).

Resumo do Fluxo

1. Codegen Spec (TS)     → Define o contrato
2. package.json config   → Configura o Codegen
3. `pod install` / build → Gera interfaces nativas
4. Implementação nativa  → Kotlin (Android) + Swift (iOS)
5. Bridge .mm (iOS)      → Conecta C++ JSI ao Swift
6. Wrapper TS            → API ergonômica para componentes
7. Build & run           → Testa no device/simulador
Enter fullscreen mode Exit fullscreen mode

Boas Práticas

  • Nomeação: O spec DEVE começar com Native (ex: NativeXyz.ts). O nome dentro de getEnforcing('Xyz') deve bater com getName() no nativo. Arquivo de spec sem "Native" no início é ignorado pelo Codegen silenciosamente
  • Tipos: Prefira tipos simples. Objetos complexos devem ser definidos inline no spec (não use import).
  • Erros: Use promise.reject(code, message) no nativo para erros tratáveis.
  • Garanta que a flag isTurboModule: true no Android, esquecer essa flag faz o módulo rodar pela bridge antiga.
  • Thread safety: Métodos são chamados na thread do JS por padrão. Use DispatchQueue.main.async (iOS) ou UiThreadUtil.runOnUiThread (Android) para operações de UI.
  • Thread safety em chamadas síncronas: Turbo Modules podem ser invocados ao mesmo tempo da thread JS. Garanta que o código nativo seja thread-safe.
  • Simulador: APIs de hardware (bateria, câmera, sensores) não funcionam no simulador iOS. Use #if targetEnvironment(simulator) para fallbacks.
  • Não commitar artefatos do Codegen: Adicione android/app/build/generated/ e ios/build/generated/ ao .gitignore.
  • Hot reload: Mudanças no código nativo exigem rebuild completo. Apenas mudanças no JS/TS usam hot reload.
  • Limpar o build após mudar specs: Qualquer mudança na spec TypeScript exige ./gradlew clean no Android e xcodebuild clean no iOS.

Conclusão

A partir do React Native 0.76, a nova arquitetura deixou de ser opcional e passou a ser o caminho padrão em produção. O que antes dependia da Bridge assíncrona e da serialização em JSON agora se apoia em quatro peças que trabalham juntas: JSI para falar direto com o nativo, Fabric para renderizar a UI com mais consistência, Turbo Modules para carregar módulos sob demanda com menor latência, e Codegen para manter o contrato entre TypeScript e Kotlin/Objective-C++ alinhado de ponta a ponta.

Neste artigo você viu por que a arquitetura antiga começou a limitar apps maiores e mais exigentes, como esses blocos se encaixam no fluxo geral, e um exemplo completo com CustomDeviceInfo — da spec Native*.ts e do codegenConfig até a implementação Android e iOS e o consumo no React. Esse fluxo é o molde: definir o contrato, deixar o Codegen gerar os stubs, implementar só a lógica nativa e expor uma API tipada no JavaScript.

Na prática, três detalhes costumam causar mais dor de cabeça do que o código em si: o arquivo de spec precisa começar com Native, o módulo Android precisa estar marcado com isTurboModule: true, e qualquer mudança na spec exige clean build antes de testar de novo. Vale tratá-los como checklist em todo módulo novo.

Se quiser ir além, a documentação oficial de Turbo Native Modules aprofunda cada etapa do Codegen e do registro nativo. E como módulos nativos costumam tocar dados sensíveis do dispositivo ou do app, combine esse conhecimento com as boas práticas de desenvolvimento seguro em React Native — o artigo já aponta para este como próximo passo na stack nativa.

Top comments (0)