TurboModules 概述
TurboModules 是 React Native 新架构的核心部分,用 JSI(JavaScript Interface)替代了旧的异步 Bridge。关键改进:
- 同步调用:JS 可以同步调用 Native 方法,无需等待异步回调
- 类型安全:通过 CodeGen 从 TypeScript 规格文件自动生成 Native 接口
- 懒加载:模块只在首次使用时才初始化,减少启动时间
定义 TypeSpec(CodeGen 规格文件)
新架构的原生模块从 TypeScript 规格文件开始:
// NativeDeviceInfo.ts(放在 src/specs/ 目录)
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
getDeviceModel(): string;
getBatteryLevel(): Promise<number>;
isCharging(): Promise<boolean>;
}
export default TurboModuleRegistry.getEnforcing<Spec>('DeviceInfo');
Android 端实现(Kotlin)
// DeviceInfoModule.kt
package com.myapp.deviceinfo
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build
import com.facebook.react.bridge.*
import com.facebook.react.module.annotations.ReactModule
@ReactModule(name = DeviceInfoModule.NAME)
class DeviceInfoModule(reactContext: ReactApplicationContext) :
NativeDeviceInfoSpec(reactContext) {
companion object {
const val NAME = "DeviceInfo"
}
override fun getName() = NAME
override fun getDeviceModel(): String {
return "${Build.MANUFACTURER} ${Build.MODEL}"
}
override fun getBatteryLevel(): Double {
val intent = reactApplicationContext.registerReceiver(
null, IntentFilter(Intent.ACTION_BATTERY_CHANGED)
)
val level = intent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
val scale = intent?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1
return if (level < 0 || scale < 0) -1.0
else level.toDouble() / scale * 100
}
override fun isCharging(): Boolean {
val bm = reactApplicationContext.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
return bm.isCharging
}
}
iOS 端实现(Objective-C / Swift)
// DeviceInfoModule.swift
import Foundation
import UIKit
@objc(DeviceInfoModule)
class DeviceInfoModule: NSObject {
@objc static func requiresMainQueueSetup() -> Bool { false }
@objc func getDeviceModel() -> String {
return UIDevice.current.model + " " + UIDevice.current.systemVersion
}
@objc func getBatteryLevel(
_ resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock
) {
UIDevice.current.isBatteryMonitoringEnabled = true
let level = UIDevice.current.batteryLevel
if level < 0 {
reject("UNAVAILABLE", "Battery monitoring not available", nil)
return
}
resolve(Int(level * 100))
}
@objc func isCharging(
_ resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock
) {
UIDevice.current.isBatteryMonitoringEnabled = true
resolve(UIDevice.current.batteryState == .charging)
}
}
JS 层封装
在 JS 层封装一个干净的 API,隐藏原生模块的复杂性:
// deviceInfo.ts
import NativeDeviceInfo from './specs/NativeDeviceInfo';
export const DeviceInfo = {
getModel(): string {
return NativeDeviceInfo.getDeviceModel();
},
async getBattery(): Promise<{ level: number; charging: boolean }> {
const [level, charging] = await Promise.all([
NativeDeviceInfo.getBatteryLevel(),
NativeDeviceInfo.isCharging(),
]);
return { level: Math.round(level), charging };
},
};
// 使用
const { level, charging } = await DeviceInfo.getBattery();
console.log(`电量: ${level}%, 充电中: ${charging}`);
发送事件到 JS(Native → JS)
// Android:发送事件
private fun sendEvent(name: String, params: WritableMap) {
reactApplicationContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(name, params)
}
// 在电量变化时触发
sendEvent("BatteryChanged", Arguments.createMap().apply {
putInt("level", newLevel)
putBoolean("charging", isCharging)
})
// JS 端监听
import { NativeEventEmitter, NativeModules } from 'react-native';
const emitter = new NativeEventEmitter(NativeModules.DeviceInfo);
useEffect(() => {
const sub = emitter.addListener('BatteryChanged', ({ level, charging }) => {
setBattery({ level, charging });
});
return () => sub.remove(); // 组件卸载时取消监听!
}, []);
调试技巧
- Android:用
adb logcat过滤 Native 日志,或在 Android Studio Logcat 中搜索模块名 - iOS:Xcode Console 查看 Swift/OC 输出,
NSLog()或print()都会显示 - JS 端:Metro 终端会显示未捕获的 Promise 错误,注意 red screen 里的堆栈信息
- CodeGen 生成的文件在
android/build/generated/和 iOS DerivedData 中,可用于确认接口签名是否正确