React Native 0.79
推荐的集成原生模块方式是Turbo Module
,使用的是cli
生成的项目:
bash
# cli 项目代码生成
npx @react-native-community/cli@latest init TurboDemo --version 0.79.0
在尝试了一下文档示例,扩展了自己集成一个 ios 微信登录的功能,下面是ios
的集成实践。
Turbo Module 主要是原生和 codegen 结合实现的
RTNCalculator示例模块结构
先简单介绍一下如何实现RTNCalculator
(示例演示用),主要参考下面文档 react-native-new-architecture 的步骤。

1.在 app 根目录创建原生模块文件夹RTNCalculator
在 RTNCalculator
中,创建三个子文件夹: js
、 ios
和 android
2.在 js 文件夹中创建NativeRTNCalculator.ts
代码如下:
ts
import { TurboModule, TurboModuleRegistry } from "react-native";
import type {EventEmitter} from 'react-native/Libraries/Types/CodegenTypes';
export interface Spec extends TurboModule {
add(a: number, b: number): Promise<number>;
readonly onValueChanged: EventEmitter<number>
}
export default TurboModuleRegistry.getEnforcing<Spec>("RTNCalculator");
3.模块配置
RTNCalculator
目录的根目录中创建 package.json
文件,主要看 codegenConfig
字段。
Codegen 配置由 codegenConfig
字段指定。它包含一个通过四个字段定义模块的对象:
name
:库的名称。按照惯例,应该添加Spec
后缀。type
:此包所含模块的类型。在本例中,它是一个 Turbo Native 模块;因此,要使用的值是modules
。jsSrcsDir
:访问 Codegen 解析的js
规范的相对路径。android.javaPackageName
: Codegen 生成的 Java 文件中使用的包
json
{
"name": "rtn-calculator",
"version": "0.0.1",
"description": "Add numbers with Turbo Native Modules",
"react-native": "js/index",
"source": "js/index",
"files": [
"js",
"android",
"ios",
"rtn-calculator.podspec",
"!android/build",
"!ios/build",
"!**/__tests__",
"!**/__fixtures__",
"!**/__mocks__"
],
"keywords": ["react-native", "ios", "android"],
"repository": "https://github.com/<your_github_handle>/rtn-calculator",
"author": "<Your Name> <your_email@your_provider.com> (https://github.com/<your_github_handle>)",
"license": "MIT",
"bugs": {
"url": "https://github.com/<your_github_handle>/rtn-calculator/issues"
},
"homepage": "https://github.com/<your_github_handle>/rtn-calculator#readme",
"devDependencies": {},
"peerDependencies": {
"react": "*",
"react-native": "*"
},
"codegenConfig": {
"name": "RTNCalculatorSpec",
"type": "modules",
"jsSrcsDir": "js",
"android": {
"javaPackageName": "com.rtncalculator"
}
}
}
接着在RTNCalculator
根目录创建 rtn-calculator.podspec
(如果有用到 ios 的一些 cocopods 依赖,写在这个文件内)文件,代码如下:
ruby
require "json"
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
Pod::Spec.new do |s|
s.name = "rtn-calculator"
s.version = package["version"]
s.summary = package["description"]
s.description = package["description"]
s.homepage = package["homepage"]
s.license = package["license"]
s.platforms = { :ios => "11.0" }
s.author = package["author"]
s.source = { :git => package["repository"], :tag => "#{s.version}" }
s.source_files = "ios/**/*.{h,m,mm,swift}"
install_modules_dependencies(s)
end
4.运行 codegen 自动生成原生代码
切换到app项目根目录,比如 TurboDemo,然后运行下面命令,会自动生成ios
的一些代码,生成的代码目录是 RTNCalculator/generated
。
bash
# codegen 生成原生代码
node ./node_modules/react-native/scripts/generate-codegen-artifacts.js \
--targetPlatform ios \
--path ./RTNCalculator \
--outputPath RTNCalculator/generated/

5.编写ios代码
创建 RTNCalculator.h
Objective-C++
#import <RTNCalculatorSpec/RTNCalculatorSpec.h>
NS_ASSUME_NONNULL_BEGIN
@interface RTNCalculator : NativeRTNCalculatorSpecBase <NativeRTNCalculatorSpec>
@end
NS_ASSUME_NONNULL_END
创建 RTNCalculator.mm
Objective-C++
#import "RTNCalculator.h"
@implementation RTNCalculator
RCT_EXPORT_MODULE()
- (void)add:(double)a b:(double)b resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
NSNumber *result = [[NSNumber alloc] initWithDouble:a+b];
resolve(result);
[self emitOnValueChanged:result];
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeRTNCalculatorSpecJSI>(params);
}
@end
6.本地安装RTNCalculator
模块
bash
# app 项目根目录下运行
npm install ./RTNCalculator
7.安装 pod 依赖
切换到 app(app项目根目录)/ios
目录下,运行 pod install
,
8.给App.tsx
添加ui操作示例
项目根目录下的App.tsx
tsx
import React, {useState} from 'react';
import {
Alert,
Button,
EventSubscription,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
import RTNCalculator from 'rtn-calculator/js/NativeRTNCalculator';
function App(): React.JSX.Element {
const [num1, setNum1] = useState<string>('3');
const [num2, setNum2] = useState<string>('7');
const [result, setResult] = useState<number | null>(null);
const listenerSubscription = React.useRef<null | EventSubscription>(null);
React.useEffect(() => {
listenerSubscription.current = RTNCalculator.onValueChanged(data => {
Alert.alert(`Result: ${data}`);
});
return () => {
listenerSubscription.current?.remove();
listenerSubscription.current = null;
};
}, []);
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
value={num1}
onChangeText={setNum1}
keyboardType="numeric"
placeholder="Enter first number"
/>
<Text style={styles.operator}>+</Text>
<TextInput
style={styles.input}
value={num2}
onChangeText={setNum2}
keyboardType="numeric"
placeholder="Enter second number"
/>
<Text style={styles.operator}>={result}</Text>
</View>
<Button
title="Calculate"
onPress={async () => {
const value1 = parseFloat(num1) || 0;
const value2 = parseFloat(num2) || 0;
const value = await RTNCalculator.add(value1, value2);
setResult(value ?? null);
}}
/>
</View>
);
}
const styles = StyleSheet.create({
inputContainer: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 20,
},
input: {
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 5,
padding: 10,
width: 100,
textAlign: 'center',
},
operator: {
fontSize: 24,
marginHorizontal: 10,
},
result: {
fontSize: 18,
marginBottom: 20,
},
});
export default App;
9.编译、运行
打开 ios 目录下 TurboDemo.xcworkspace
,配置好红框内的 bundle 签名信息,然后运行,成功以后截图如下:



ios 微信 sdk 集成示例
操作步骤同上面RTNCalculator
一样,创建模块文件夹为RTNWechat
,下面贴出文件代码
1.RTNWechat/js/NativeRTNWechat.ts
文件:
ts
import { TurboModule, TurboModuleRegistry } from "react-native";
import type { EventEmitter } from 'react-native/Libraries/Types/CodegenTypes';
export interface Spec extends TurboModule {
// 检查微信是否已安装
isWXAppInstalled(): Promise<boolean>;
// 微信登录
login(): Promise<{
code: string;
state: string;
}>;
// 微信支付
pay(params: {
partnerId: string;
prepayId: string;
nonceStr: string;
timeStamp: string;
package: string;
sign: string;
}): Promise<{
errCode: number;
errStr: string;
}>;
// 微信分享(链接)
shareLink(params: {
title: string;
description: string;
thumbUrl: string;
webpageUrl: string;
scene: number; // 0: 会话 1: 朋友圈 2: 收藏
}): Promise<{
errCode: number;
errStr: string;
}>;
// 微信分享(图片)
shareImage(params: {
image: string;
scene: number; // 0: 会话 1: 朋友圈 2: 收藏
}): Promise<{
errCode: number;
errStr: string;
}>;
// 事件监听器 - 处理从原生端返回的事件
readonly onAuthResponse: EventEmitter<{
errCode: number;
errStr: string;
code?: string;
state?: string;
}>;
}
export default TurboModuleRegistry.get<Spec>("RTNWechat") as Spec | null;
2.RTNWechat/ios/RTNWechat.h
文件:
Objective-C++
#import <RTNWechatSpec/RTNWechatSpec.h>
NS_ASSUME_NONNULL_BEGIN
@interface RTNWechat : NativeRTNWechatSpecBase <NativeRTNWechatSpec>
@end
NS_ASSUME_NONNULL_END
3.RTNWechat/ios/RTNWechat.mm
文件:
Objective-C++
#import "RTNWechat.h"
#import <WXApi.h>
@interface RTNWechat() <WXApiDelegate>
@end
@implementation RTNWechat
RCT_EXPORT_MODULE()
// 在这里初始化微信SDK
-(instancetype)init {
self = [super init];
if (self) {
NSLog(@"RTNWechat模块初始化");
BOOL result = [WXApi registerApp:@"wxd477edab60670232" universalLink:@"https://www.baidu.com"];
NSLog(@"微信SDK自动注册结果: %@", result ? @"成功" : @"失败");
// 检查微信是否已安装
BOOL isInstalled = [WXApi isWXAppInstalled];
NSLog(@"微信是否已安装: %@", isInstalled ? @"是" : @"否");
// 检查微信是否支持API
BOOL isSupport = [WXApi isWXAppSupportApi];
NSLog(@"微信是否支持API: %@", isSupport ? @"是" : @"否");
}
return self;
}
// 确保在主线程执行UI操作
- (dispatch_queue_t)methodQueue {
return dispatch_get_main_queue();
}
// 检查微信是否已安装
RCT_EXPORT_METHOD(isWXAppInstalled:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
BOOL isInstalled = [WXApi isWXAppInstalled];
NSLog(@"RTNWechat isWXAppInstalled: %@", isInstalled ? @"已安装" : @"未安装");
resolve(@(isInstalled));
}
// 登录
- (void)login:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
// 检查微信是否已安装
if (![WXApi isWXAppInstalled]) {
reject(@"login_error", @"WeChat is not installed", nil);
return;
}
// 打印当前注册状态
NSLog(@"WXApi isWXAppSupport: %d", [WXApi isWXAppInstalled]);
SendAuthReq *req = [[SendAuthReq alloc] init];
req.scope = @"snsapi_userinfo";
req.state = @"wechat_login";
[WXApi sendReq:req completion:^(BOOL success) {
NSLog(@"WXApi sendReq result: %d", success);
if (success) {
// 请求发送成功,等待回调
resolve(nil); // 注意:我们应该在这里resolve,否则promise会一直pending
} else {
reject(@"login_error", @"Failed to send login request", nil);
}
}];
}
// 支付
- (void)pay:(NSDictionary *)params resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
// 检查参数
if (!params[@"partnerId"] || !params[@"prepayId"] || !params[@"nonceStr"] ||
!params[@"timeStamp"] || !params[@"package"] || !params[@"sign"]) {
reject(@"pay_error", @"Missing required payment parameters", nil);
return;
}
// 检查微信是否已安装
if (![WXApi isWXAppInstalled]) {
reject(@"pay_error", @"WeChat is not installed", nil);
return;
}
PayReq *req = [[PayReq alloc] init];
req.partnerId = params[@"partnerId"];
req.prepayId = params[@"prepayId"];
req.nonceStr = params[@"nonceStr"];
req.timeStamp = [params[@"timeStamp"] intValue];
req.package = params[@"package"];
req.sign = params[@"sign"];
[WXApi sendReq:req completion:^(BOOL success) {
if (success) {
// 请求发送成功,等待回调
resolve(nil); // 添加resolve解决Promise
} else {
reject(@"pay_error", @"Failed to send payment request", nil);
}
}];
}
// 分享链接
- (void)shareLink:(NSDictionary *)params resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
// 检查参数
if (!params[@"webpageUrl"] || !params[@"title"]) {
reject(@"share_error", @"Missing required share link parameters", nil);
return;
}
// 检查微信是否已安装
if (![WXApi isWXAppInstalled]) {
reject(@"share_error", @"WeChat is not installed", nil);
return;
}
WXWebpageObject *webpage = [WXWebpageObject object];
webpage.webpageUrl = params[@"webpageUrl"];
WXMediaMessage *message = [WXMediaMessage message];
message.title = params[@"title"];
message.description = params[@"description"] ?: @"";
// 处理缩略图 - 异步加载以避免阻塞主线程
if (params[@"thumbUrl"]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:params[@"thumbUrl"]]];
UIImage *thumbImage = imageData ? [UIImage imageWithData:imageData] : nil;
dispatch_async(dispatch_get_main_queue(), ^{
if (thumbImage) {
[message setThumbImage:thumbImage];
}
message.mediaObject = webpage;
SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
req.bText = NO;
req.message = message;
req.scene = [params[@"scene"] intValue];
[WXApi sendReq:req completion:^(BOOL success) {
if (success) {
// 请求发送成功,等待回调
resolve(nil); // 添加resolve解决Promise
} else {
reject(@"share_error", @"Failed to send share request", nil);
}
}];
});
});
} else {
// 无缩略图的情况
message.mediaObject = webpage;
SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
req.bText = NO;
req.message = message;
req.scene = [params[@"scene"] intValue];
[WXApi sendReq:req completion:^(BOOL success) {
if (success) {
// 请求发送成功,等待回调
resolve(nil); // 添加resolve解决Promise
} else {
reject(@"share_error", @"Failed to send share request", nil);
}
}];
}
}
// 分享图片
- (void)shareImage:(NSDictionary *)params resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
// 检查参数
if (!params[@"image"]) {
reject(@"share_error", @"Missing image URL", nil);
return;
}
// 检查微信是否已安装
if (![WXApi isWXAppInstalled]) {
reject(@"share_error", @"WeChat is not installed", nil);
return;
}
// 异步加载图片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:params[@"image"]]];
if (!imageData) {
dispatch_async(dispatch_get_main_queue(), ^{
reject(@"share_error", @"Failed to load image", nil);
});
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
WXImageObject *imageObject = [WXImageObject object];
imageObject.imageData = imageData;
WXMediaMessage *message = [WXMediaMessage message];
message.mediaObject = imageObject;
// 创建缩略图
UIImage *image = [UIImage imageWithData:imageData];
UIImage *thumbImage = [self thumbnailWithImage:image size:CGSizeMake(100, 100)];
[message setThumbImage:thumbImage];
SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
req.bText = NO;
req.message = message;
req.scene = [params[@"scene"] intValue];
[WXApi sendReq:req completion:^(BOOL success) {
if (success) {
// 请求发送成功,等待回调
resolve(nil); // 添加resolve解决Promise
} else {
reject(@"share_error", @"Failed to send share request", nil);
}
}];
});
});
}
// 创建缩略图方法
- (UIImage *)thumbnailWithImage:(UIImage *)image size:(CGSize)size {
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
[image drawInRect:CGRectMake(0, 0, size.width, size.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
// 处理微信回调
- (void)onResp:(BaseResp *)resp {
if ([resp isKindOfClass:[SendAuthResp class]]) {
// 登录回调
SendAuthResp *authResp = (SendAuthResp *)resp;
[self emitOnAuthResponse:@{
@"errCode": @(authResp.errCode),
@"errStr": authResp.errStr ?: @"",
@"code": authResp.code ?: @"",
@"state": authResp.state ?: @""
}];
} else if ([resp isKindOfClass:[PayResp class]]) {
// 支付回调
PayResp *payResp = (PayResp *)resp;
[self emitOnAuthResponse:@{
@"errCode": @(payResp.errCode),
@"errStr": payResp.errStr ?: @""
}];
} else if ([resp isKindOfClass:[SendMessageToWXResp class]]) {
// 分享回调
SendMessageToWXResp *shareResp = (SendMessageToWXResp *)resp;
[self emitOnAuthResponse:@{
@"errCode": @(shareResp.errCode),
@"errStr": shareResp.errStr ?: @""
}];
}
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeRTNWechatSpecJSI>(params);
}
@end
4.rtn-wechat.podspec
文件
ruby
require "json"
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
Pod::Spec.new do |s|
s.name = "rtn-wechat"
s.version = package["version"]
s.summary = package["description"]
s.description = package["description"]
s.homepage = package["homepage"]
s.license = package["license"]
s.platforms = { :ios => "11.0" }
s.author = package["author"]
s.source = { :git => package["repository"], :tag => "#{s.version}" }
s.source_files = "ios/**/*.{h,m,mm,swift}"
install_modules_dependencies(s)
# 微信SDK依赖
s.dependency "WechatOpenSDK", "~> 2.0.2"
end
5.package.json
文件
json
{
"name": "rtn-wechat",
"version": "0.0.1",
"description": "WeChat login, payment and sharing with Turbo Native Modules",
"react-native": "js/index",
"source": "js/index",
"files": [
"js",
"android",
"ios",
"rtn-wechat.podspec",
"!android/build",
"!ios/build",
"!**/__tests__",
"!**/__fixtures__",
"!**/__mocks__"
],
"keywords": [
"react-native",
"ios",
"android"
],
"repository": "https://github.com/<your_github_handle>/rtn-wechat",
"author": "<Your Name> <your_email@your_provider.com> (https://github.com/<your_github_handle>)",
"license": "MIT",
"bugs": {
"url": "https://github.com/<your_github_handle>/rtn-wechat/issues"
},
"homepage": "https://github.com/<your_github_handle>/rtn-wechat#readme",
"devDependencies": {},
"peerDependencies": {
"react": "*",
"react-native": "*"
},
"codegenConfig": {
"name": "RTNWechatSpec",
"type": "modules",
"jsSrcsDir": "js",
"android": {
"javaPackageName": "com.rtnwechat"
}
}
}
6.增加 info.plist
配置
xml
<key>LSApplicationQueriesSchemes</key>
<array>
<string>weixin</string>
<string>weixinULAPI</string>
</array>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>weixin</string>
<key>CFBundleURLSchemes</key>
<array>
<string>您的微信AppID</string>
</array>
</dict>
</array>
需要注意的点:
使用 Codegen 生成脚手架代码时,iOS 不会自动清理 build
文件夹。例如,如果您更改了 Spec 名称,然后再次运行 Codegen ,旧文件将会被保留。如果发生这种情况,请记住在再次运行 Codegen 之前删除 build
文件夹。
如果修改了 ios 原生代码,需要用 xcode 重新编译运行
实机演示

示例仓库代码
文档参考
reactnative.dev/docs/turbo-...
developers.weixin.qq.com/doc/oplatfo...
扩展文档
题外话
也尝试使用过 Expo 集成原生模块功能,但是需要 prebuild 出 ios 和 Android 目录来,和 cli 创建的模块方法类似,也需要遵守定义的那一套规则。
一般企业用的 app 大多都会需要第三方登录(如微信登录、QQ 登录、微博登录、fb 登录登功能)、第三方支付功能(微信支付、支付宝支付)、统计类(友盟统计)、极光推送、穿山甲等一些 sdk 的集成。这些功能都需要自己集成,如果是需要这种第三方 sdk 比较多的,还是建议使用 cli 的方式项目。
