名称: react-native-native-modules 用户可调用: false 描述: 在构建或集成React Native中的原生模块时使用。涵盖创建原生模块、Turbo Modules、桥接原生代码和访问平台特定API。 允许工具:
- 读取
- 写入
- 编辑
- Bash
- Grep
- Glob
React Native 原生模块
使用此技能当创建自定义原生模块、集成第三方原生库或访问通过JavaScript不可用的平台特定功能时。
关键概念
原生模块概述
原生模块桥接JavaScript和原生代码:
JavaScript层
↕ (桥接)
原生层 (iOS/Android)
Turbo Modules (现代方法)
Turbo Modules 提供更好的性能和类型安全:
// NativeMyModule.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
getString(value: string): Promise<string>;
getNumber(value: number): number;
getBoolean(value: boolean): boolean;
getArray(value: Array<any>): Array<any>;
getObject(value: Object): Object;
}
export default TurboModuleRegistry.getEnforcing<Spec>('MyModule');
从JS调用原生代码
import { NativeModules } from 'react-native';
const { MyModule } = NativeModules;
// 调用原生方法
async function callNativeMethod() {
try {
const result = await MyModule.getString('Hello from JS');
console.log(result);
} catch (error) {
console.error('原生模块错误:', error);
}
}
最佳实践
iOS原生模块 (Swift)
在Swift中创建原生模块:
// MyModule.swift
import Foundation
@objc(MyModule)
class MyModule: NSObject {
@objc
func getString(_ value: String,
resolver: @escaping RCTPromiseResolveBlock,
rejecter: @escaping RCTPromiseRejectBlock) {
// 处理值
let result = "已处理: \(value)"
resolver(result)
}
@objc
func getNumber(_ value: NSNumber) -> NSNumber {
let doubled = value.doubleValue * 2
return NSNumber(value: doubled)
}
@objc
static func requiresMainQueueSetup() -> Bool {
return false
}
}
// MyModule.m (桥接文件)
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(MyModule, NSObject)
RCT_EXTERN_METHOD(getString:(NSString *)value
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(getNumber:(nonnull NSNumber *)value)
@end
Android原生模块 (Kotlin)
在Kotlin中创建原生模块:
// MyModule.kt
package com.myapp
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Promise
class MyModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
override fun getName(): String {
return "MyModule"
}
@ReactMethod
fun getString(value: String, promise: Promise) {
try {
val result = "已处理: $value"
promise.resolve(result)
} catch (e: Exception) {
promise.reject("ERROR", e.message)
}
}
@ReactMethod
fun getNumber(value: Double): Double {
return value * 2
}
}
// MyModulePackage.kt
package com.myapp
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
class MyModulePackage : ReactPackage {
override fun createNativeModules(
reactContext: ReactApplicationContext
): List<NativeModule> {
return listOf(MyModule(reactContext))
}
override fun createViewManagers(
reactContext: ReactApplicationContext
): List<ViewManager<*, *>> {
return emptyList()
}
}
TypeScript包装器
创建类型安全的包装器:
// MyModule.ts
import { NativeModules, Platform } from 'react-native';
interface MyModuleInterface {
getString(value: string): Promise<string>;
getNumber(value: number): number;
getBoolean(value: boolean): boolean;
}
const LINKING_ERROR =
`包 'react-native-my-module' 似乎未链接。请确保:
` +
Platform.select({ ios: "- 运行 'pod install'
", default: '' }) +
'- 重新构建应用';
const MyModule: MyModuleInterface = NativeModules.MyModule
? NativeModules.MyModule
: new Proxy(
{},
{
get() {
throw new Error(LINKING_ERROR);
},
}
);
export default MyModule;
原生事件
从原生发送事件到JavaScript:
// iOS - MyModule.swift
import Foundation
@objc(MyModule)
class MyModule: RCTEventEmitter {
override func supportedEvents() -> [String]! {
return ["onDataReceived"]
}
@objc
func startListening() {
// 模拟接收数据
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.sendEvent(withName: "onDataReceived",
body: ["data": "来自原生的问候!"])
}
}
}
// Android - MyModule.kt
class MyModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
private fun sendEvent(eventName: String, params: WritableMap?) {
reactApplicationContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(eventName, params)
}
@ReactMethod
fun startListening() {
val params = Arguments.createMap()
params.putString("data", "来自原生的问候!")
sendEvent("onDataReceived", params)
}
}
// JavaScript
import { NativeEventEmitter, NativeModules } from 'react-native';
import { useEffect } from 'react';
function useNativeEvent() {
useEffect(() => {
const eventEmitter = new NativeEventEmitter(NativeModules.MyModule);
const subscription = eventEmitter.addListener('onDataReceived', (event) => {
console.log('从原生接收:', event.data);
});
NativeModules.MyModule.startListening();
return () => subscription.remove();
}, []);
}
常见模式
相机访问
// JavaScript API
interface CameraModule {
takePicture(): Promise<string>; // 返回图片URI
requestPermissions(): Promise<boolean>;
}
// iOS实现
import UIKit
import AVFoundation
@objc(CameraModule)
class CameraModule: NSObject {
@objc
func requestPermissions(_ resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
AVCaptureDevice.requestAccess(for: .video) { granted in
resolve(granted)
}
}
@objc
func takePicture(_ resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
// 实现相机捕获
// 返回图片URI
resolve("file:///path/to/image.jpg")
}
}
// Android实现
class CameraModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
@ReactMethod
fun requestPermissions(promise: Promise) {
// 检查和请求相机权限
val hasPermission = ContextCompat.checkSelfPermission(
reactApplicationContext,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
promise.resolve(hasPermission)
}
@ReactMethod
fun takePicture(promise: Promise) {
// 实现相机捕获
promise.resolve("file:///path/to/image.jpg")
}
}
生物识别认证
// JavaScript API
interface BiometricModule {
authenticate(reason: string): Promise<{ success: boolean; error?: string }>;
isAvailable(): Promise<boolean>;
}
// iOS实现
import LocalAuthentication
@objc(BiometricModule)
class BiometricModule: NSObject {
@objc
func isAvailable(_ resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
let context = LAContext()
var error: NSError?
let available = context.canEvaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
error: &error
)
resolve(available)
}
@objc
func authenticate(_ reason: String,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
let context = LAContext()
context.evaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
localizedReason: reason
) { success, error in
if success {
resolve(["success": true])
} else {
resolve(["success": false, "error": error?.localizedDescription ?? ""])
}
}
}
}
设备信息模块
// JavaScript API
interface DeviceInfoModule {
getDeviceId(): string;
getDeviceName(): string;
getSystemVersion(): string;
getBatteryLevel(): Promise<number>;
}
// iOS实现
import UIKit
@objc(DeviceInfoModule)
class DeviceInfoModule: NSObject {
@objc
func getDeviceId() -> String {
return UIDevice.current.identifierForVendor?.uuidString ?? ""
}
@objc
func getDeviceName() -> String {
return UIDevice.current.name
}
@objc
func getSystemVersion() -> String {
return UIDevice.current.systemVersion
}
@objc
func getBatteryLevel(_ resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
UIDevice.current.isBatteryMonitoringEnabled = true
let level = UIDevice.current.batteryLevel
resolve(level)
}
}
原生UI组件
// 自定义原生视图
import { requireNativeComponent, ViewProps } from 'react-native';
interface MapViewProps extends ViewProps {
region: {
latitude: number;
longitude: number;
latitudeDelta: number;
longitudeDelta: number;
};
onRegionChange?: (event: any) => void;
}
export const MapView = requireNativeComponent<MapViewProps>('MapView');
// 用法
<MapView
region={{
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
onRegionChange={(event) => console.log(event.nativeEvent)}
/>
反模式
不要阻塞主线程
// 错误 - 阻塞主线程
@objc
func heavyComputation(_ value: String,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
let result = performHeavyWork(value) // 阻塞UI
resolve(result)
}
// 正确 - 使用后台线程
@objc
func heavyComputation(_ value: String,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
DispatchQueue.global(qos: .userInitiated).async {
let result = self.performHeavyWork(value)
resolve(result)
}
}
不要忘记错误处理
// 错误 - 无错误处理
@ReactMethod
fun readFile(path: String, promise: Promise) {
val content = File(path).readText()
promise.resolve(content)
}
// 正确 - 适当的错误处理
@ReactMethod
fun readFile(path: String, promise: Promise) {
try {
val file = File(path)
if (!file.exists()) {
promise.reject("FILE_NOT_FOUND", "文件不存在")
return
}
val content = file.readText()
promise.resolve(content)
} catch (e: Exception) {
promise.reject("READ_ERROR", e.message, e)
}
}
不要泄漏内存
// 错误 - 强引用循环
class MyModule: NSObject {
var timer: Timer?
@objc
func startTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
self.doSomething() // 对self的强引用
}
}
}
// 正确 - 弱引用
class MyModule: NSObject {
var timer: Timer?
@objc
func startTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.doSomething()
}
}
deinit {
timer?.invalidate()
}
}
不要使用同步操作
// 错误 - 同步网络调用
@ReactMethod
fun fetchData(url: String): String {
return URL(url).readText() // 阻塞线程
}
// 正确 - 异步与promise
@ReactMethod
fun fetchData(url: String, promise: Promise) {
Thread {
try {
val data = URL(url).readText()
promise.resolve(data)
} catch (e: Exception) {
promise.reject("FETCH_ERROR", e.message)
}
}.start()
}
相关技能
- react-native-platform: 平台特定代码模式
- react-native-components: 集成原生组件
- react-native-performance: 原生性能优化