React Native’de “JS’ten native çağır” ihtiyacı iki nesil API ile karşılanır: klasik Native Modules ve New Architecture’ın Turbo Modules’u. İkisi aynı problemi çözer gibi görünür; sözleşme, keşif, threading ve tooling kökten farklıdır.
Karar “hepsini migrate et” veya “legacy yeter” değil; çağrı sıklığı, tip riski ve New Arch roadmap’ine göre önceliktir.
Klasik Native Modules: runtime keşif
Eski stil native çağrı örneği:
import { NativeModules } from 'react-native';
const { DeviceInfo } = NativeModules;
await DeviceInfo.getBatteryLevel();
Native taraf uygulama başlangıcında module map’i JS’e açar. Bridge (eski JSON köprüsü) üzerinden async mesaj gider, cevap Promise ile döner.
Artı: basit model, bol dokümantasyon, eski kütüphaneler hazır. Eksi: yanlış modül adı → undefined (compile-time yakalanmaz); performans bridge ile sınırlı; New Arch’da interop (geçici köprü katmanı) maliyeti.
getConstants() erken hydrate için kullanışlıdır ama eager init maliyeti yaratır.
Turbo Modules: compile-time sözleşme
Spec üzerinden tip güvenli çağrı:
import NativeDeviceInfo from './NativeDeviceInfo';
const level = await NativeDeviceInfo.getBatteryLevel();
Turbo Modules Spec-first yaklaşım kullanır: TypeScript/Flow ile API tanımlarsınız, codegen (kod üreteci) platform iskeletini üretir. JSI (doğrudan çağrı) ile lazy init — modül ilk çağrıda yüklenir. TurboModuleRegistry.getEnforcing fail-fast sunar: modül yoksa erken patlar, sessiz undefined değil.
Sync çağrı mümkündür — dikkatli kullanıldığında. Storage okuma gibi milisaniye altı işler için bridge overhead’i kalkar. Sync JS thread’i bloklama riski taşır.
Derin karşılaştırma
| Boyut | Native Modules | Turbo Modules |
|---|---|---|
| Keşif | Runtime module map | Spec + codegen |
| Taşıma | Bridge JSON | JSI binding |
| Init | Genelde eager | Lazy (ilk çağrıda) |
| Tip güvenliği | Zayıf | Güçlü (Spec) |
| Sync çağrı | Pratikte yok | Mümkün (disiplin şart) |
| New Arch uyumu | Interop | Birinci sınıf |
| Öğrenme eğrisi | Düşük | Orta-yüksek |
Interop “çalışıyor” demek “optimal” demek değildir. Hot path’teki legacy modül her çağrıda ekstra maliyet öder.
Performans: sayılarla düşünmek
Bridge async çağrısı tipik olarak yüzlerce mikrosaniye ile birkaç milisaniye overhead taşır. Saniyede 10 çağrı tolere edilir; saniyede 1000 analytics event bridge üzerinden frame drop üretir.
Sync Turbo serileştirmeyi sıfırlar; native implementasyonunuz 5ms sürüyorsa JS thread 5ms bloklanır. Kazanç her zaman “Turbo’ya geç” değil; doğru API + doğru thread’dir.
Ne zaman migrate etmeli?
- Sık çağrılan API: storage, feature flag, analytics batch
- New Arch 6 ay içinde production’da
- Kütüphane yayınlıyorsunuz
- Tip hataları production’a sızıyor
- Sync okuma ihtiyacı var
- Yılda bir kez çağrılan sarmalayıcı
- Yakında silinecek ekran
- Vendor SDK henüz New Arch dokümantasyonu sunmuyor
- Surface küçük ve crash riski sıfır
Karar: çağrı_sıklığı × tip_riski × New_Arch_hedefi. Yüksek × yüksek × evet = migrate now.
Migrasyon pattern’i
Birinci kural: API davranış parity’si. Spec’i mevcut API’den çıkarın; JS tüketicileri değişmemeli.
- Tüm
NativeModules.*envanteri; hot path’leri işaretleyin - Spec yazın + codegen (Android + iOS)
- Facade’da Turbo + legacy fallback
- Feature flag ile cohort rollout
- Metrikler stabil → legacy kaldırın
- ESLint: doğrudan
NativeModulesimport yasak
Facade örneği — Turbo varsa onu, yoksa legacy kullanır:
import { NativeModules } from 'react-native';
import NativeSample from './specs/NativeSample';
export function add(a: number, b: number): number {
if (NativeSample) {
return NativeSample.add(a, b);
}
return NativeModules.SampleLegacy.add(a, b);
}
Codegen çıktısını commitlemeyin — her build’de üretilsin. CI’da codegen adımını quality gate’e ekleyin.
View’lar ayrı konu
Native module Turbo’ya geçmek, native component’in Fabric’e geçmesi demek değildir. Eski ViewManager → Fabric Spec ayrı migrasyon. Sadece Turbo’ya geçip custom view’ları Paper’da bırakmak hibrit acı üretir.
Platform parity ve R8
Nullable dönüş, array vs object, enum representation platformlar arası test edilmeli.
R8 obfuscation Turbo kayıt sınıflarını silebilir. proguard-rules.pro’da codegen keep kuralları güncel tutun. Release’de “modül bulunamadı” çoğu zaman keep eksikliğidir.
Production checklist
- Tüm
NativeModules.*envanteri çıkarıldı - Hot path’ler önceliklendirildi
- Spec + codegen Android/iOS yeşil
- Facade + legacy fallback test edildi
- Interop’tan çıkış tarihi backlog’da
- R8/ProGuard keep güncel
- New Arch açık/kapalı matrix test
- Custom view varsa Fabric planı var
- ESLint: NativeModules import yasak
Özet
Native Modules RN’i büyüttü; Turbo Modules onu ölçeklenebilir native sınıra taşır. Legacy’yi şeytanlaştırmak yerine hangi yüzeyin hâlâ bridge ekonomisinde yaşadığını bilerek migrate edin. Interop geçici köprüdür — kalıcı mimari değil.