React NativeExpoMetroJSITurboModulesFabricCodegenHermesMobile Development

React Native + Expo + Metro + Implementação Nativa: Guia Completo

Sloth255
Sloth255
·9 min read·1,998 words

Quando se começa a usar React Native, é fácil ter a sensação de que "funciona, mas sem entender bem por quê". Neste artigo, vamos explicar tudo o que acontece por baixo dos panos — de Metro, JSI, Codegen e como o Expo funciona, até a implementação nativa para áreas que o SDK do Expo não consegue cobrir.


1. A Filosofia Central do React Native

O coração do React Native é traduzir a lógica de UI escrita em JS para componentes UI nativos. Assim como o ReactDOM converte <div> em elementos DOM, o React Native mapeia <View> e <Text> para UIView no iOS e android.view.View no Android.

Isso significa que ele gera UI nativa real — não renderiza HTML em uma WebView. Essa é a diferença fundamental em relação ao Cordova ou Ionic.


2. Evolução da Arquitetura: De Bridge para JSI

O React Native atualmente tem duas arquiteturas coexistentes.

flowchart LR
  subgraph OLD["Arquitetura Antiga (Bridge)"]
    direction TB
    J1["Thread JS<br/>Lógica React"] -->|"Serializar JSON"| BR["Bridge<br/>Fila assíncrona ⚠"]
    BR -->|"Desserializar JSON"| N1["Thread Nativo<br/>UIKit / Android View"]
    SH["Thread Shadow<br/>Layout Yoga"] --> N1
  end
  subgraph NEW["Nova Arquitetura (JSI)"]
    direction TB
    HM["Motor Hermes<br/>Execução bytecode"] -->|"Referência C++ direta"| JSI["JSI<br/>Host Objects"]
    JSI --> FAB["Fabric<br/>Renderizador síncrono"]
    JSI --> TM["TurboModules<br/>Inicialização lazy"]
    FAB --> N2["Camada Nativa"]
    TM --> N2
  end

Problemas da Arquitetura Antiga (Bridge)

O React Native legado fazia os threads JS e nativo se comunicarem através de um único gargalo assíncrono chamado Bridge. Cada troca precisava ser serializada como JSON, enviada e desserializada — tornando operações frequentes como animações e gestos propensas a problemas de desempenho.

Inovações da Nova Arquitetura (JSI + Hermes)

JSI (JavaScript Interface) é uma camada de binding delgada em C++ que permite ao motor JS referenciar objetos nativos diretamente e de forma síncrona. Isso possibilitou:

  • Fabric — Um novo renderizador que constrói e atualiza árvores de UI sincronamente
  • TurboModules — Inicialização lazy que adia o carregamento de módulos nativos até que sejam necessários
  • Hermes — O motor do Facebook que pré-compila JS para bytecode, reduzindo drasticamente o tempo de inicialização

3. Codegen: A Chave para Comunicação Nativa com Segurança de Tipos

Codegen é o mecanismo mais negligenciado na nova arquitetura. Enquanto JSI e TurboModules tornam a comunicação possível, o Codegen garante que ela seja correta em tempo de compilação.

flowchart TD
  SPEC["Arquivo JS Spec<br/>Definições de tipos em TypeScript / Flow"]

  subgraph BUILD["Tempo de Compilação"]
    CG["Codegen<br/>Executa durante pod install / Gradle"]
  end

  subgraph OUT["Artefatos Gerados"]
    CPP["Classes C++ Abstratas<br/>Interfaces base TurboModule"]
    DESC["Component Descriptor<br/>Para componentes nativos Fabric"]
    JSTYPE["Definições de Tipos JS<br/>Equivalente a .d.ts"]
  end

  subgraph NATIVE["Implementação Nativa"]
    IOS["iOS<br/>Implementado em Swift / Obj-C"]
    AND["Android<br/>Implementado em Kotlin / Java"]
  end

  RUNTIME["Via JSI<br/>Seguro em tipos, chamadas síncronas"]

  SPEC --> CG
  CG --> CPP & DESC & JSTYPE
  CPP --> IOS & AND
  DESC --> IOS & AND
  IOS --> RUNTIME
  AND --> RUNTIME

Na arquitetura antiga, a interface entre JS e nativo era essencialmente um "acordo de cavalheiros". Chamar NativeModules.MyModule.doSomething(42) do JS só geraria um erro ao ocorrer um crash em tempo de execução se o lado nativo esperasse uma String.

O Codegen resolve isso em tempo de compilação. Os desenvolvedores escrevem uma Spec (especificação) em TypeScript ou Flow descrevendo quais tipos um módulo nativo aceita. Durante pod install (iOS) ou a compilação Gradle (Android), o Codegen gera automaticamente classes C++ abstratas, andaime nativo para iOS e Android e definições de tipos JS.


4. O Pipeline do Metro Bundler

Metro é o bundler JavaScript dedicado do React Native. Cumpre o mesmo papel que o webpack, mas é otimizado para desenvolvimento móvel.

flowchart LR
  SRC[".js / .ts / .tsx<br/>Arquivos fonte"]
  RES["Resolução<br/>Resolução de caminhos de import"]
  TRA["Transformação<br/>Transforms ES via Babel"]
  SER["Serialização<br/>Geração do bundle"]
  DEV["Entrega ao dispositivo<br/>HTTP :8081"]
  HMR["HMR<br/>Reenviar apenas módulos alterados"]
  CACHE["Cache FS<br/>Mais rápido a partir do 2° início"]

  SRC --> RES --> TRA --> SER --> DEV
  TRA -.->|"Detecção de mudança de arquivo"| HMR
  HMR -.->|"Refletir sem perder o state"| DEV
  TRA <-.->|"Cachear resultados transformados"| CACHE

O pipeline do Metro tem três fases:

  • Resolução — Resolução de módulos no estilo Node.js com seleção automática de arquivos específicos por plataforma (foo.ios.ts / foo.android.ts)
  • Transformação — Babel converte JSX, TypeScript e ES moderno; resultados são cacheados para inícios mais rápidos
  • Serialização — Gera o bundle final e o entrega ao dispositivo via servidor HTTP

Notavelmente, o HMR (Hot Module Replacement) atualiza apenas o grafo dos módulos alterados, refletindo mudanças de código imediatamente enquanto preserva o estado dos componentes React. Builds de produção combinam --minify e a compilação Hermes em bytecode para reduzir tanto o tamanho do arquivo quanto o tempo de inicialização.


5. A Estrutura em Camadas do Expo e as Ferramentas de Desenvolvimento

flowchart TD
  APP["Seu App<br/>App.tsx / screens / hooks"]
  SDK["Expo SDK<br/>expo-camera / expo-location / expo-router …"]
  EMC["expo-modules-core<br/>DSL com segurança de tipos em Swift / Kotlin"]
  RN["React Native Core<br/>View / Text / Animated / StyleSheet"]
  IOS["iOS<br/>UIKit / AVFoundation / CoreLocation"]
  AND["Android<br/>View / Camera2 / LocationManager"]
  GO["Expo Go<br/>Iniciar instantaneamente via QR"]
  EASB["EAS Build<br/>Gerar .ipa / .apk na nuvem"]
  EASU["EAS Update<br/>Entregar apenas JS bundle via OTA"]
  STORE["App Store / Google Play"]

  APP --> SDK
  EMC --> SDK
  SDK --> RN
  RN --> IOS & AND
  GO -->|"Hospedar durante o dev"| APP
  EASB --> STORE
  EASU -->|"Sem revisão App Store"| APP

O SDK do Expo fornece funcionalidades nativas — câmera, localização, notificações, sistema de arquivos — como pacotes versionados. Sem necessidade de escrever código nativo próprio — esse é seu maior valor.

expo-modules-core é um sistema mais novo que permite o desenvolvimento declarativo de módulos nativos com DSLs Swift e Kotlin, integrado ao Codegen. É mais seguro em tipos e menos verboso que o antigo bridge NativeModules, e quase todos os SDKs de terceiros do Expo o utilizam.

EAS (Expo Application Services) é um termo genérico para Build, Update, Submit, Workflows e mais. Este artigo foca em EAS Build e EAS Update. O EAS Build compila binários nativos na nuvem, possibilitando builds iOS sem Mac. O EAS Update entrega bundles JS e assets via OTA, permitindo entrega rápida de mudanças que não tocam código nativo.


6. Áreas que o Expo não Cobre e a Escolha do Workflow

Principais áreas que o SDK do Expo não cobre:

  • Bluetooth LE / NFC — Comunicação com periféricos específicos da plataforma
  • SDKs personalizados de pagamento/biometria — Integração direta de .framework / .aar de fornecedores
  • Processamento de áudio/vídeo em tempo real — WebRTC de baixo nível ou operações com codecs personalizados
  • Bibliotecas nativas internas — Assets Swift / Kotlin extraídos de apps existentes
  • Views de câmera personalizadas — Renderização personalizada com OpenGL / Metal
flowchart TD
  START["Qual funcionalidade nativa você precisa?"]

  START --> Q1{"Coberta pelo SDK Expo<br/>ou biblioteca pública?"}
  Q1 -->|"Sim"| MANAGED["Managed Workflow<br/>Sem código nativo necessário<br/>Apenas npx expo start"]
  Q1 -->|"Não"| Q2{"Apenas mudanças de configuração?<br/>(Adições Info.plist / AndroidManifest)"}
  Q2 -->|"Sim"| PLUGIN["Criar Config Plugin<br/>Adicionar ao app.json<br/>Aplicado automaticamente pelo prebuild"]
  Q2 -->|"Não"| Q3{"Volume de código nativo personalizado?"}
  Q3 -->|"Leve<br/>(wrapper de SDK existente)"| BARE["Bare Workflow<br/>npx expo prebuild<br/>→ Gerenciar ios/ android/ diretamente"]
  Q3 -->|"Completo<br/>(módulo / UI personalizado)"| MODULE{"O que está sendo construído?"}
  MODULE -->|"Lógica<br/>Bluetooth / pagamento / cripto"| TM["TurboModule<br/>+ Codegen"]
  MODULE -->|"Componente UI<br/>View de câmera personalizada, etc."| FAB["Fabric Component<br/>+ Codegen"]
  BARE --> TM & FAB
  PLUGIN --> BARE

Mudanças de configuração menores podem ser tratadas com Config Plugins. Mas se for necessário escrever código, gerar os diretórios ios/ e android/ com npx expo prebuild e migrar para o Bare Workflow é o ponto de partida prático.


7. Arquitetura Completa: JSI + Codegen + Implementação Nativa

flowchart TB
  subgraph JS["Camada JS (Hermes)"]
    APP["Código do App / Componentes React"]
    SPEC["JS Spec<br/>(TypeScript + TurboModuleRegistry)"]
  end

  subgraph CODEGEN["Codegen (Auto-gerado em tempo de compilação)"]
    GEN_CPP["Classes C++ Abstratas<br/>TurboModule / ComponentDescriptor"]
    GEN_SHADOW["Definições ShadowNode / Props"]
  end

  subgraph JSI_LAYER["Camada JSI (C++)"]
    JSI["JSI Host Object"]
    FABRIC["Fabric Renderer"]
  end

  subgraph NATIVE_IOS["Implementação Nativa iOS"]
    SWIFT["Implementação Swift / Obj-C<br/>(Herda classe C++ abstrata)"]
    SDK_IOS["SDK Interno / .framework Externo<br/>ex: CoreBluetooth / Stripe"]
    UIVIEW["Subclasse UIView<br/>(Componente UI Personalizado)"]
  end

  subgraph NATIVE_AND["Implementação Nativa Android"]
    KOTLIN["Implementação Kotlin / Java<br/>(Herda classe C++ abstrata)"]
    SDK_AND["SDK Interno / .aar Externo<br/>ex: BluetoothGatt / Stripe"]
    ANDROIDVIEW["Subclasse View<br/>(Componente UI Personalizado)"]
  end

  SPEC -->|"Leitura das definições de tipos"| CODEGEN
  GEN_CPP --> JSI
  GEN_SHADOW --> FABRIC
  APP -->|"Chamadas síncronas / assíncronas"| JSI
  JSI --> SWIFT & KOTLIN
  FABRIC --> UIVIEW & ANDROIDVIEW
  SWIFT --> SDK_IOS
  KOTLIN --> SDK_AND

A chave está na dependência unidirecional: Spec → Codegen → Implementação Nativa. Escreva definições de tipos no lado JS, e o Codegen gera classes C++ abstratas em tempo de compilação. As implementações Swift / Kotlin só precisam heroá-las — a consistência de tipos é garantida pelo compilador, eliminando o "acordo de cavalheiros" como fonte de crashes em tempo de execução.


8. Fluxo de Implementação TurboModule (Exemplo Bluetooth)

Nota: O código a seguir é um exemplo simplificado baseado nos guias oficiais do React Native para TurboModule / Codegen / Custom Events. Não são citações diretas dos exemplos oficiais, mas uma reconstrução usando Bluetooth como exemplo.

① Escrever a JS Spec

// src/specs/NativeBluetoothModule.ts
import type { TurboModule, CodegenTypes } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export type ScanResult = {
  id: string;
  name: string;
  rssi: number;
};

export interface Spec extends TurboModule {
  startScan(serviceUUIDs: string[]): void;
  stopScan(): void;
  connect(deviceId: string): Promise<boolean>;
  readonly onDeviceFound: CodegenTypes.EventEmitter<ScanResult>;
}

export default TurboModuleRegistry.getEnforcing<Spec>('NativeBluetoothModule');

② O que o Codegen Gera (Automaticamente)

flowchart LR
  SPEC["NativeBluetoothModule.ts<br/>(Escrito pelo desenvolvedor)"]

  subgraph AUTO["Código auto-gerado"]
    CPP["Código C++ glue<br/>Saída JSI / Codegen"]
    IOS_H["Código iOS glue<br/>Saída Obj-C++ / header"]
    AND_J["Código Android glue<br/>Saída JNI / classe Spec"]
  end

  subgraph IMPL["Implementado pelo desenvolvedor"]
    SWIFT_IMPL["NativeBluetoothModule.swift<br/>Implementa Spec gerada, chama CoreBluetooth"]
    KOTLIN_IMPL["NativeBluetoothModule.kt<br/>Implementa Spec gerada, chama BluetoothGatt"]
  end

  SPEC --> CPP
  SPEC --> IOS_H
  SPEC --> AND_J
  CPP --> SWIFT_IMPL & KOTLIN_IMPL
  IOS_H --> SWIFT_IMPL
  AND_J --> KOTLIN_IMPL

③ Implementação iOS (Swift)

// ios/NativeBluetoothModule.swift
import CoreBluetooth

@objc(NativeBluetoothModule)
class NativeBluetoothModule: NativeBluetoothModuleSpec, CBCentralManagerDelegate {
  private var centralManager: CBCentralManager!

  override func startScan(_ serviceUUIDs: [String]) {
    let uuids = serviceUUIDs.map { CBUUID(string: $0) }
    centralManager.scanForPeripherals(withServices: uuids)
  }

  func centralManager(_ central: CBCentralManager,
                      didDiscover peripheral: CBPeripheral,
                      advertisementData: [String: Any], rssi: NSNumber) {
    emitOnDeviceFound([
      "id": peripheral.identifier.uuidString,
      "name": peripheral.name ?? "",
      "rssi": rssi
    ])
  }
}

④ Implementação Android (Kotlin)

// android/src/main/java/NativeBluetoothModule.kt
class NativeBluetoothModule(reactContext: ReactApplicationContext)
    : NativeBluetoothModuleSpec(reactContext), BluetoothGatt.Callback {

  private val adapter = BluetoothAdapter.getDefaultAdapter()

  override fun startScan(serviceUUIDs: ReadableArray) {
    val filters = serviceUUIDs.toArrayList().map { uuid ->
      ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString(uuid as String)).build()
    }
    adapter.bluetoothLeScanner.startScan(filters, ScanSettings.Builder().build(), scanCallback)
  }

  private val scanCallback = object : ScanCallback() {
    override fun onScanResult(callbackType: Int, result: ScanResult) {
      val params = Arguments.createMap().apply {
        putString("id", result.device.address)
        putString("name", result.device.name ?: "")
        putInt("rssi", result.rssi)
      }
      emitOnDeviceFound(params)
    }
  }
}

9. Fluxo de Implementação Fabric Component (Exemplo View de Câmera Personalizada)

Se TurboModule lida com a lógica, o Fabric Component serve para incorporar UI nativa no JSX. Necessário para renderização personalizada com OpenGL / Metal, ou reutilização de views nativas existentes.

Nota: O seguinte também é um exemplo mínimo para compreensão. Caminhos de import do Codegen e nomes de tipos variam conforme a versão do React Native, sempre consulte a documentação oficial da sua versão ao implementar.

// src/specs/NativeCameraViewSpec.ts
import type { ViewProps } from 'react-native';
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
import type { DirectEventHandler, Float } from 'react-native/Libraries/Types/CodegenTypes';

type FrameCapturedEvent = Readonly<{
  uri: string;
}>;

interface NativeProps extends ViewProps {
  zoom?: Float;          // Codegen mapeia Float → CGFloat / float
  torchEnabled?: boolean;
  onFrameCaptured?: DirectEventHandler<FrameCapturedEvent>;
}

export default codegenNativeComponent<NativeProps>('CameraView');

O Codegen gera definições C++ para ShadowNode, ComponentDescriptor e Props a partir desta Spec. No iOS, implemente um CameraViewComponentView herdando de RCTViewComponentView; no Android, herdar de ReactViewGroup. Do lado JS, é tão simples quanto <CameraView zoom={2.0} torchEnabled />.


10. Integração de SDKs Internos / .framework e .aar Externos

iOS: Referência Local via CocoaPods

# ios/Podfile
pod 'CompanyPaymentSDK', :path => '../vendor/ios/CompanyPaymentSDK'
# ou para xcframework
pod 'CompanyPaymentSDK', :podspec => '../vendor/ios/CompanyPaymentSDK.podspec'

Após adicionar ao Podfile e executar pod install, pode ser usado como uma classe Swift normal com import CompanyPaymentSDK. Chamar diretamente da implementação TurboModule.

Android: Maven Local / Referência .aar

// android/app/build.gradle
dependencies {
  implementation files('../vendor/android/company-payment-sdk.aar')
  // ou um repositório Maven interno
  implementation 'com.company:payment-sdk:1.4.0'
}

11. O Loop de Desenvolvimento: Do Início à Verificação

flowchart TD
  INIT["npx create-expo-app MyApp<br/>--template blank-typescript"]

  subgraph START["npx expo start"]
    METRO["Metro Bundler inicia<br/>porta 8081"]
    QR["QR code + atalhos<br/>i = iOS  a = Android  w = Web"]
  end

  subgraph DEVICE["Escolher alvo"]
    SIM["iOS Simulator<br/>Incluído com Xcode (apenas Mac)"]
    EMU["Android Emulator<br/>Android Studio AVD"]
    GO["Dispositivo físico + Expo Go<br/>Via LAN ou Tunnel"]
  end

  subgraph NATIVE_BUILD["Apenas na primeira vez: Build Nativo"]
    POD["pod install<br/>iOS CocoaPods"]
    GRADLE["Gradle build<br/>Android"]
    CODEGEN_RUN["Codegen executa<br/>Spec → Andaime nativo"]
  end

  BUNDLE["Transferir JS bundle ao dispositivo<br/>via servidor HTTP do Metro"]
  RENDER["Tela é exibida"]

  INIT --> START
  METRO --> QR
  QR --> SIM & EMU & GO
  SIM --> NATIVE_BUILD
  EMU --> NATIVE_BUILD
  NATIVE_BUILD --> BUNDLE
  GO --> BUNDLE
  BUNDLE --> RENDER

Execute npx expo start para iniciar o Metro, e um QR code com atalhos de teclado aparece no terminal.

  • Tecla i → iOS Simulator (funciona no Mac com Xcode instalado)
  • Tecla a → Android Emulator (dispositivo virtual criado no Android Studio AVD Manager)
  • Para dispositivos físicos, escanear o QR no Expo Go — conecta quase instantaneamente em LAN

Ao acionar explicitamente um build nativo com npx expo run:ios ou npx expo run:android, pod install (iOS) ou Gradle (Android) é executado pela primeira vez e o Codegen gera o andaime nativo. Execuções posteriores transferem apenas diferenças do JS bundle e são muito mais rápidas.


12. O Loop de Mudanças no Bare Workflow

flowchart TD
  subgraph CHANGE["Ramificação por tipo de mudança"]
    JS_ONLY["Apenas mudanças JS<br/>Componentes / lógica / estilos"]
    NATIVE_CHANGE["Mudanças de código nativo<br/>Swift / Kotlin / Spec / Podfile"]
  end

  subgraph JS_LOOP["Loop de Mudança JS (Rápido)"]
    METRO_HMR["Metro HMR<br/>Transferir bundle diferencial<br/>~Centenas de ms"]
    SIM_JS["Visível imediatamente no emulador<br/>Estado do app preservado"]
  end

  subgraph NATIVE_LOOP["Loop de Mudança Nativo (Lento)"]
    CODEGEN_RUN["Re-executar Codegen<br/>pod install (iOS)<br/>Gradle sync (Android)"]
    REBUILD["Rebuild nativo<br/>npx expo run:ios<br/>npx expo run:android"]
    SIM_NATIVE["Verificar no emulador / dispositivo"]
  end

  JS_ONLY --> METRO_HMR --> SIM_JS
  NATIVE_CHANGE --> CODEGEN_RUN --> REBUILD --> SIM_NATIVE

  SIM_JS -->|"Sem problemas"| DONE["Verificação OK"]
  SIM_NATIVE -->|"Sem problemas"| DONE
  SIM_JS -->|"Bug encontrado"| DEBUG_JS["Hermes Debugger<br/>React DevTools"]
  SIM_NATIVE -->|"Crash nativo"| DEBUG_NATIVE["Xcode / Android Studio<br/>Depurador nativo"]
  DEBUG_JS --> JS_ONLY
  DEBUG_NATIVE --> NATIVE_CHANGE

Mudanças apenas de JS são tratadas pelo Metro HMR em centenas de milissegundos, preservando o estado.

Mudanças de código nativo exigem um rebuild. O primeiro build iOS leva 2–5 minutos, builds diferenciais cerca de 30 segundos a um minuto. Android com cache Gradle é similar; o primeiro build leva mais tempo.

Essa assimetria é a chave para a eficiência de desenvolvimento no Bare Workflow: fixar cedo o limite nativo (Spec) e empurrar a maior parte da implementação para o lado JS para reduzir a frequência de builds nativos pesados.


13. Depuração: Investigação Através do Limite JS/Nativo

flowchart TD
  SYMPTOM["Observar sintomas"]

  SYMPTOM --> Q1{"Tipo de crash"}

  Q1 -->|"Erro JS<br/>Tela vermelha LogBox"| JS_ERR["Investigar com Hermes Debugger<br/>Breakpoints, stack trace<br/>Números de linha TS via Source Map"]
  Q1 -->|"Crash nativo<br/>App congela / crasha"| NAT_ERR["Verificar logs de crash"]
  Q1 -->|"Funciona mas<br/>resultado errado"| BORDER["Suspeitar do limite JS↔Nativo"]

  NAT_ERR --> IOS_CRASH["iOS: Xcode<br/>Debug Navigator<br/>+ anexar lldb"]
  NAT_ERR --> AND_CRASH["Android: Android Studio<br/>Stack trace Logcat<br/>+ Native Debugger"]

  BORDER --> LOG_IOS["iOS: NSLog / os_log<br/>npx react-native log-ios"]
  BORDER --> LOG_AND["Android: Log.d<br/>npx react-native log-android"]
  BORDER --> JS_LOG["Lado JS: console.log<br/>Verificar valores de retorno TurboModule"]

  IOS_CRASH --> INSTRUMENTS["Instruments<br/>Time Profiler / Allocations<br/>Identificar vazamentos de memória / CPU"]
  AND_CRASH --> PROFILER["Android Studio<br/>Memory / CPU Profiler"]

  JS_ERR --> FIXED["Corrigir → Visível via Metro HMR"]
  INSTRUMENTS --> FIXED2["Corrigir → Verificar com npx expo run:ios"]
  PROFILER --> FIXED3["Corrigir → Verificar com npx expo run:android"]
  LOG_IOS & LOG_AND & JS_LOG --> BORDER2["Identificar causa raiz<br/>→ Corrigir Spec / implementação"]

Erros JS: Hermes Debugger

Cmd+D → "Open Debugger" abre o Chrome DevTools. O Metro fornece Source Maps, permitindo colocar breakpoints nos números de linha TypeScript originais — não no código bundleado. Exceções em chamadas TurboModule mostram o stack completo de volta ao lado JS.

Crashes Nativos: Anexar Depurador do Xcode / Android Studio

# iOS: Anexar lldb ao simulador em execução
npx expo run:ios --configuration Debug
# → Xcode abre automaticamente e anexa o depurador

# Android
npx expo run:android
# → "Run > Attach Debugger to Android Process"

Para crashes que só se reproduzem em dispositivos físicos (especialmente Bluetooth, câmera, sensores), o depurador pode ser anexado da mesma forma a um dispositivo conectado via USB.

Investigação de Limite: Encapsular com Logs

Quando dados são corrompidos no limite JS↔Nativo, encapsulá-los com logs de ambos os lados é a abordagem mais rápida.

// Lado Swift
os_log("BluetoothModule.connect called: %{public}@", deviceId)
// → Filtrar com npx react-native log-ios
// Lado Kotlin
Log.d("BluetoothModule", "connect called: $deviceId")
// → Filtrar com npx react-native log-android
// Lado JS
const result = await BluetoothModule.connect(deviceId);
console.log('connect result:', result); // Saída no Metro

React DevTools

npx react-devtools
# → Aguarda conexão do dispositivo na porta 8097

Visualize a árvore de componentes e inspecione (e modifique) props e state em tempo real.


14. Simuladores vs. Dispositivos Físicos

O que está sendo testado iOS Simulator Android Emulator Dispositivo Físico
UI / Layout Suficiente Suficiente Verificação final
Lógica JS / API Suficiente Suficiente Não necessário
Bluetooth / NFC Impossível Impossível Obrigatório
Câmera / Microfone Parcial Impossível Obrigatório
Notificações push Parcial Parcial Recomendado
Performance Apenas referência Apenas referência Obrigatório
Autenticação biométrica Sim Face ID disponível Sim impressão digital disponível Recomendado

Para desenvolvimento com módulos nativos, uma abordagem em duas etapas funciona bem: consolidar o comportamento JS nos emuladores, verificar funcionalidades nativas em dispositivos físicos. Especialmente para Bluetooth e NFC — já que emuladores não conseguem testá-los — o número de dispositivos e a cobertura de versões de SO impacta diretamente a qualidade.


Resumo

O Stack Tecnológico Completo

Camada Tecnologia Papel
Bundler Metro Transpilação de fontes, HMR, ramificação por plataforma
Motor JS Hermes Execução bytecode para inicialização rápida
Bridge JS↔Nativo JSI Binding síncrono via C++
Geração de Código Codegen Auto-geração de andaime nativo com segurança de tipos a partir da Spec
Renderizador UI Fabric Renderização de UI síncrona sobre JSI
APIs Nativas TurboModules Módulos com inicialização lazy herdando classes geradas pelo Codegen
Infraestrutura Dev Expo / EAS SDK, builds na nuvem, atualizações OTA

Fluxo Completo para Implementação Nativa

① Escrever a JS Spec (definições de tipos em TypeScript)
       ↓
② pod install / gradle build → Codegen auto-gera andaime C++
       ↓
③ Implementar em Swift / Kotlin (chamar SDK interno / bibliotecas externas)
       ↓
④ npx expo run:ios / run:android → Build nativo & verificação no emulador
       ↓
⑤ Escrever código de chamada JS → Iteração rápida via Metro HMR
       ↓
⑥ Erros JS → Hermes Debugger
   Crashes nativos → Depurador Xcode / Android Studio
   Discrepâncias no limite → Encapsular com log-ios / log-android + console.log
       ↓
⑦ Verificar Bluetooth / câmera / sensores em dispositivos físicos
       ↓
⑧ EAS Build gera binários prontos para as lojas

A transição do React Native de Bridge para JSI + Codegen o transformou de "funciona mas frágil" para "com segurança de tipos e alto desempenho". Sair do Expo traz custos — builds pesados e escrever a mesma lógica em duas linguagens — mas o Codegen torna a Spec a única fonte da verdade, eliminando o maior risco: crashes em tempo de execução por incompatibilidades de tipos, já na fase de design.


URLs de Referência

Nota: Este artigo resume e reconstrói informações das seguintes fontes. Exemplos e explicações foram simplificados para maior clareza e não são citações diretas. Sempre consulte a documentação oficial da versão que está usando ao implementar.

URLs Referenciadas

React Native Oficial

Expo Oficial