Module 10 — Native Modules & New Architecture

Nên có ⏱ 6-8 giờ 📋 Prerequisites: Module 01-04, 08

🎯 Mục tiêu học tập

📖 Hướng dẫn học

1 Đọc RN New Architecture docsreactnative.dev/docs/new-architecture-intro
Focus: Why, What changes, Migration path. Thời gian: ~2h
2 Xem "React Native New Architecture" talk — App.js Conf (YouTube)
Thời gian: ~1h
3 Đọc JSI blog postscallstack.com/blog (search "JSI")
Thời gian: ~1h
4 Enable New Architecture trong 1 project thật
Theo guide: android/gradle.properties → newArchEnabled=true, iOS Podfile → ENV['RCT_NEW_ARCH_ENABLED'] = '1'
Thời gian: ~2h (bao gồm fix issues)

⚡ Ôn nhanh

🧭 Vòng lặp học module này

📚 Lý thuyết chi tiết

1. Old Bridge Architecture

Cách hoạt động: JS thread và Native thread giao tiếp qua Bridge — 1 async message queue. Mọi data phải JSON serialize/deserialize khi qua Bridge.

Data flow:

JS Thread                    Bridge                    Native Thread
   |                           |                           |
   |-- JSON.stringify(msg) --> |                           |
   |                           |-- JSON.parse(msg) ------>|
   |                           |                           |
   |                           |<-- JSON.stringify(res) --|
   |<-- JSON.parse(res) ----- |                           |

Bottlenecks:

2. JSI (JavaScript Interface)

Thay thế Bridge bằng gì? JSI tạo C++ host objects mà JS có thể gọi trực tiếp, synchronously. Không serialization. Không async queue.

Cơ chế: JSI expose C++ objects vào JS runtime (Hermes). JS gọi method trên object đó → thực thi C++ code trực tiếp, không qua Bridge.

// Old Bridge: async, serialized
NativeModules.BatteryModule.getBatteryLevel()
  .then(level => console.log(level));

// New JSI: synchronous, direct
const level = global.__turboModuleProxy.BatteryModule.getBatteryLevel();

Tại sao game-changer:

3. TurboModules

Thay thế NativeModules cũ.

Old NativeModulesTurboModules
LoadingEager (tất cả load khi app start)Lazy (load khi lần đầu sử dụng)
CommunicationBridge (async, serialized)JSI (sync, direct)
Type safetyKhông (runtime errors)Có (Codegen từ JS/TS specs)
Startup impactCao (load tất cả)Thấp (lazy)

4. Fabric (New Rendering System)

Thay thế old UI Manager. Fabric = new rendering system dựa trên JSI.

5. Codegen

Auto-generate native interfaces (Java/ObjC/C++) từ JavaScript/TypeScript type specifications. Đảm bảo type safety giữa JS và native code tại build time.

// JS Spec file: NativeBatteryModule.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  getBatteryLevel(): number;
  isCharging(): boolean;
}

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

Codegen sẽ generate native interface code từ spec này → compile-time errors nếu native implementation không match.

6. Native Module Authoring

Khi nào cần viết Native Module?

Android (Kotlin) — Basic Pattern

class BatteryModule(reactContext: ReactApplicationContext) :
  ReactContextBaseJavaModule(reactContext) {

  override fun getName() = "BatteryModule"

  @ReactMethod
  fun getBatteryLevel(promise: Promise) {
    val batteryManager = reactApplicationContext
      .getSystemService(Context.BATTERY_SERVICE) as BatteryManager
    val level = batteryManager
      .getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
    promise.resolve(level)
  }
}

iOS (Swift) — Basic Pattern

@objc(BatteryModule)
class BatteryModule: NSObject {
  @objc func getBatteryLevel(
    _ resolve: @escaping RCTPromiseResolveBlock,
    rejecter reject: @escaping RCTPromiseRejectBlock
  ) {
    UIDevice.current.isBatteryMonitoringEnabled = true
    let level = UIDevice.current.batteryLevel
    resolve(Int(level * 100))
  }

  @objc static func requiresMainQueueSetup() -> Bool {
    return false
  }
}

7. NativeEventEmitter

Send events từ native → JS (push-based, không phải request-response).

import { NativeEventEmitter, NativeModules } from 'react-native';

const emitter = new NativeEventEmitter(NativeModules.BatteryModule);

useEffect(() => {
  const subscription = emitter.addListener('BatteryChanged', (event) => {
    setBatteryLevel(event.level);
  });
  return () => subscription.remove();
}, []);

8. Ví dụ thực tế: JS contract trước khi viết native

// BatteryModule.ts
export type BatteryStatus = {
  level: number;
  isCharging: boolean;
};

export interface BatteryModuleApi {
  getStatus(): Promise<BatteryStatus>;
  startObserving(): void;
  stopObserving(): void;
}

// Component chỉ phụ thuộc contract này, không phụ thuộc Android/iOS detail.

Tip: Trước khi viết Kotlin/Swift, chốt API JS/TS trước: method nào async, event nào push lên JS, lỗi trả về format gì, permission được xử lý ở đâu.

❓ Câu hỏi phỏng vấn + Đáp án

Q1: Bridge bottleneck là gì? New Architecture giải quyết thế nào?

Bridge problems: Async-only communication, JSON serialization/deserialization overhead, single-threaded queue, eager module loading.

New Architecture fixes: JSI cho synchronous calls không serialization. TurboModules cho lazy loading. Fabric cho concurrent rendering + sync layout. Codegen cho type safety.

Q2: JSI là gì? Khác Bridge thế nào?

JSI = C++ interface cho phép JS gọi native functions trực tiếp, synchronously, không serialization. Bridge = async message queue với JSON serialization. JSI nhanh hơn orders of magnitude vì không có serialization overhead và direct memory access.

Q3: TurboModules vs old NativeModules?

TurboModules: lazy loading (load khi dùng), JSI-based (sync), type-safe (Codegen). Old NativeModules: eager loading (tất cả load khi start), Bridge-based (async), no type safety. TurboModules cải thiện startup time + runtime performance đáng kể.

Q4: Fabric giải quyết vấn đề gì?

Fabric cho phép synchronous layout measurement (trước đây phải async → layout flicker), concurrent rendering (React 18 features), priority-based rendering (user input trước data fetch), shared C++ Yoga engine giữa JS và native.

Q5: Codegen làm gì?

Auto-generate native interface code (Java/ObjC/C++) từ TypeScript specs. Đảm bảo JS ↔ Native contract type-safe tại build time thay vì runtime crashes. Developer viết TS spec → Codegen generate native boilerplate.

Q6: Khi nào cần viết Native Module?

Khi cần: platform-specific SDK (payment gateways), hardware APIs (Bluetooth, NFC, biometrics), CPU-intensive tasks (image processing), hoặc khi không có community library phù hợp. Luôn check xem có library sẵn trước khi viết native module.

🏋️ Bài tập

Bài 1: Write Battery Level Native Module
Viết native module đọc battery level cho cả Android (Kotlin) và iOS (Swift).
Yêu cầu: Promise-based API, NativeEventEmitter cho battery change events, proper error handling.
Bonus: Migrate sang TurboModule spec.
Bài 2: Enable New Architecture
Tạo 1 RN project mới → enable New Architecture → verify build thành công trên cả iOS và Android.
Document: những issues gặp phải và cách fix.

⚠️ Lỗi thường gặp

🏢 Case đi làm thật

Case: App tích hợp SDK thanh toán native. Android trả lỗi dạng code/message, iOS throw string. JS layer phải xử lý nhiều nhánh và bug khó test.

Cách xử lý: Chuẩn hóa error ở native boundary thành { code, message, recoverable }. Viết adapter JS và test mock module trước khi nối SDK thật.

🧠 Ghi nhớ nhanh

Native module tốt bắt đầu từ contract JS rõ ràng. Kotlin/Swift chỉ là implementation detail phía sau contract đó.

❔ Câu hỏi tự kiểm tra

1. Khi nào native module nên trả Promise, khi nào nên emit event?
2. Bridge cũ nghẽn ở đâu?
3. Trước khi bật New Architecture cho app cũ, bạn kiểm tra gì?
4. Native module cần test ở những boundary nào?

✅ Checklist tự đánh giá

📎 Tài nguyên

📘 RN New Architecture Docs
📘 TurboModules Guide
📝 JSI Explained — Callstack Blog
📺 Search "React Native New Architecture" trên YouTube — App.js Conf talks
📘 RN Interview Questions — Architecture section