实战:准备与原生代码编写
在React Native开发中,原生模块扩展允许开发者调用平台原生功能。以下是创建和集成原生模块的完整步骤。
项目初始化与静态库配置
首先,使用react-native init nativeTest创建一个新的React Native项目。然后,在Xcode中打开项目文件nativeTest/ios/nativeTest.xcodeproj。
接下来,创建静态库并链接到项目:
- 在
nativeTest/node_modules目录下创建文件夹react-native-nativeModuleTest,并在其中创建ios子目录。 - 在Xcode中新建一个Cocoa Touch Static Library,命名为
nativeModuleTest。 - 将生成的静态库文件拷贝到
nativeTest/node_modules/react-native-nativeModuleTest/ios目录中。 - 在Xcode中打开
nativeModuleTest.xcodeproj,配置Header Search Paths为$(SRCROOT)/../../react-native/React,并设置为recursive。 - 将
nativeModuleTest.xcodeproj拖拽到主项目的Library中。 - 在Build Phases的Link Binary With Libraries部分添加
libnativeModuleTest.a静态库。
编写原生模块代码
在React Native中,原生模块是一个实现RCTBridgeModule协议的Objective-C类。RCT是ReaCT的缩写。
创建头文件nativeModuleTest.h:
#import <React/RCTBridgeModule.h>
#import <React/RCTLog.h>
@interface nativeModuleTest : NSObject <RCTBridgeModule>
@end
实现文件nativeModuleTest.m:
#import "nativeModuleTest.h"
#import <React/RCTBridge.h>
#import <React/RCTEventDispatcher.h>
@implementation nativeModuleTest
RCT_EXPORT_MODULE(nativeModuleTest);
// 暴露一个打印方法
RCT_EXPORT_METHOD(testPrint:(NSString *)name info:(NSDictionary *)info) {
RCTLogInfo(@"%@: %@", name, info);
}
// 获取屏幕尺寸的辅助函数
static NSDictionary *DynamicDimensions() {
CGFloat width = MIN(RCTScreenSize().width, RCTScreenSize().height);
CGFloat height = MAX(RCTScreenSize().width, RCTScreenSize().height);
CGFloat scale = RCTScreenScale();
if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) {
width = MAX(RCTScreenSize().width, RCTScreenSize().height);
height = MIN(RCTScreenSize().width, RCTScreenSize().height);
}
return @{@"width": @(width), @"height": @(height), @"scale": @(scale)};
}
// 通过回调返回屏幕尺寸
RCT_EXPORT_METHOD(getDynamicDimensions:(RCTResponseSenderBlock)callback) {
callback(@[[NSNull null], DynamicDimensions()]);
}
// 通过Promise返回屏幕尺寸
RCT_REMAP_METHOD(getDynamicDimensionsByPromise,
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
NSDictionary *dimensions = DynamicDimensions();
if (dimensions) {
resolve(dimensions);
} else {
reject(@"-1001", @"not respond this method", nil);
}
}
// 初始化并监听屏幕方向变化
- (instancetype)init {
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(orientationDidChange:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
@synthesize bridge = _bridge;
// 发送方向变化事件到JavaScript
- (void)orientationDidChange:(id)noti {
[_bridge.eventDispatcher sendDeviceEventWithName:@"orientationDidChange"
body:@{@"Orientation": UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation) ? @"Landscape" : @"Portrait",
@"Dimensions": DynamicDimensions()}];
}
// 导出常量供JavaScript使用
- (NSDictionary *)constantsToExport {
return @{@"EVENT_ORIENTATION": @"orientationDidChange"};
}
@end
JavaScript调用示例
在JavaScript中,可以这样使用原生模块:
import { NativeModules, DeviceEventEmitter } from 'react-native';
const nativeModuleTest = NativeModules.nativeModuleTest;
// 调用打印方法
nativeModuleTest.testPrint("Jack", { height: '1.78m', weight: '7kg' });
// 通过回调获取屏幕尺寸
nativeModuleTest.getDynamicDimensions((error, dimensions) => {
console.log("屏幕尺寸:", dimensions);
});
// 通过Promise获取屏幕尺寸
async function fetchDimensions() {
try {
const dimensions = await nativeModuleTest.getDynamicDimensionsByPromise();
console.log("屏幕尺寸(Promise):", dimensions);
} catch (e) {
console.error(e);
}
}
fetchDimensions();
// 监听屏幕方向变化事件
const subscription = DeviceEventEmitter.addListener('orientationDidChange', (dimensions) => {
console.log("屏幕旋转,新尺寸:", dimensions);
});
// 在组件卸载时取消订阅
// componentWillUnmount() { subscription.remove(); }
通信方式详解
React Native中,JavaScript与原生模块之间的通信主要有三种方式,各有优缺点。
JavaScript向原生模块传输数据
通过直接调用原生模块暴露的方法,并传递参数。这种方式只能调用一次。
示例:在JavaScript中调用nativeModuleTest.testPrint("Jack", info),原生模块通过RCT_EXPORT_METHOD接收参数并处理。
原生模块向JavaScript传输数据
-
回调函数(Callbacks):原生方法可以接受一个
RCTResponseSenderBlock参数,用于将数据传回JavaScript。回调函数第一个参数是错误对象(无错误时为null),其余为返回值。注意:回调通常只能调用一次,且必须调用以避免内存泄漏。
示例:getDynamicDimensions方法使用回调返回屏幕尺寸。 -
Promise:如果原生方法的最后两个参数是
RCTPromiseResolveBlock和RCTPromiseRejectBlock,则对应的JavaScript方法会返回Promise对象,便于使用async/await语法。
示例:getDynamicDimensionsByPromise方法通过Promise解析屏幕尺寸或拒绝错误。
发送事件(多次数据传输)
原生模块可以通过RCTEventDispatcher主动向JavaScript发送事件,即使未被调用。适用于持续数据流,如屏幕方向监听。
示例:在orientationDidChange方法中,使用_bridge.eventDispatcher发送方向变化事件,JavaScript通过DeviceEventEmitter订阅。
发布上线
完成开发后,可以将原生模块发布为npm包供他人使用。
创建GitHub仓库
在模块目录nativeTest/node_modules/react-native-nativeModuleTest中初始化Git仓库,并关联到远程GitHub仓库。
创建入口文件
创建index.js文件作为模块入口:
import { NativeModules } from 'react-native';
module.exports = NativeModules.nativeModuleTest;
发布到npm
- 运行
npm init生成package.json文件,确保包含基本信息如名称、版本、入口文件和GitHub仓库链接。如果模块依赖其他包,需在dependencies中声明。 - 注册npm账号(如果尚未注册),使用
npm adduser命令。 - 执行
npm publish发布模块。发布成功后,其他开发者可通过npm install react-native-nativeModuleTest安装使用。
通过以上步骤,你可以完整地创建、测试和发布一个React Native原生模块,实现JavaScript与原生平台的高效通信。