RN原生模块交互:打通JS与原生的桥梁
在前几篇文章中,我们掌握了RN的基础组件、路由管理和状态管理,能够开发常规的跨端应用。但在实际业务中,RN的JS层能力有限,很多场景(如调用手机传感器、集成原生SDK、自定义高性能UI)需要依赖原生代码。本文作为RN体系化专栏的第四篇,将深入讲解RN与原生模块的交互原理 、Android/iOS原生模块开发 、第三方SDK集成,帮助你打通JS与原生的通信壁垒,实现RN能力的深度扩展。
一、RN与原生通信原理:JSBridge
RN的跨端能力本质是通过JSBridge实现JS层与原生层的双向通信,其核心是一个异步消息传递机制,工作流程可分为三步:
- JS层发起调用 :JS通过
NativeModules调用原生模块方法,参数经序列化后通过JSBridge发送给原生层; - 原生层处理请求:原生层接收消息后,解析参数并执行对应逻辑,完成后将结果序列化返回;
- JS层接收结果:JSBridge将原生返回的结果传递给JS层回调函数,完成一次通信。
RN的原生模块交互分为两种模式:
- JS调用原生:如调用原生相机、获取设备信息;
- 原生调用JS:如原生监听硬件事件后通知JS层(如电量变化、推送消息)。
二、JS调用原生模块(基础篇)
RN已内置部分原生模块(如Alert、AsyncStorage),可直接通过react-nativeAPI调用,这是最基础的原生交互方式。
1. 调用RN内置原生模块
以Alert(弹窗)和Linking(打开链接)为例,无需编写原生代码即可直接使用:
jsx
import { View, Text, TouchableOpacity, StyleSheet, Alert, Linking } from 'react-native';
export default function BuiltInModuleDemo() {
// 调用原生弹窗
const showAlert = () => {
Alert.alert(
'原生弹窗',
'这是RN内置的原生Alert组件',
[
{ text: '取消', style: 'cancel' },
{ text: '确定', onPress: () => console.log('确定按钮点击') }
]
);
};
// 调用原生打开浏览器
const openBrowser = () => {
Linking.openURL('https://reactnative.dev').catch(err =>
console.error('打开链接失败:', err)
);
};
return (
<View style={styles.container}>
<TouchableOpacity style={styles.btn} onPress={showAlert}>
<Text style={styles.btnText}>显示原生弹窗</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.btn, { marginTop: 15 }]} onPress={openBrowser}>
<Text style={styles.btnText}>打开浏览器</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f5f5f5',
},
btn: {
backgroundColor: '#0066cc',
paddingHorizontal: 30,
paddingVertical: 12,
borderRadius: 8,
},
btnText: {
color: '#fff',
fontSize: 16,
},
});
2. 自定义原生模块(Android端)
当内置模块无法满足需求时,需自定义原生模块。以下实现一个Android原生模块,提供获取设备型号和自定义Toast的能力。
(1)创建Android原生模块类
在Android工程的app/src/main/java/com/[项目名]/modules目录下,创建DeviceModule.java:
java
package com.rndemo.modules;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
import android.os.Build;
import android.widget.Toast;
// 自定义原生模块,需继承ReactContextBaseJavaModule
public class DeviceModule extends ReactContextBaseJavaModule {
// 构造方法,接收React上下文
public DeviceModule(ReactApplicationContext reactContext) {
super(reactContext);
}
// 重写getName方法,定义模块名称(JS层通过此名称调用)
@Override
public String getName() {
return "DeviceModule";
}
// 定义JS可调用的方法,需添加@ReactMethod注解
// 方法必须为void类型,异步结果通过Promise返回
@ReactMethod
public void getDeviceModel(Promise promise) {
try {
// 获取设备型号
String model = Build.MODEL;
// 成功返回结果
promise.resolve(model);
} catch (Exception e) {
// 失败返回错误
promise.reject("ERROR", e.getMessage());
}
}
// 自定义Toast提示
@ReactMethod
public void showCustomToast(String message) {
ReactApplicationContext context = getReactApplicationContext();
// 调用Android原生Toast
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
}
}
(2)注册原生模块
创建模块注册类CustomPackage.java,将自定义模块注册到RN中:
java
package com.rndemo.modules;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CustomPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
// 注册自定义模块
modules.add(new DeviceModule(reactContext));
return modules;
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
(3)在MainApplication中配置Package
修改MainApplication.java,添加自定义Package:
java
import com.rndemo.modules.CustomPackage;
// ...
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// 添加自定义Package
packages.add(new CustomPackage());
return packages;
}
(4)JS层调用自定义原生模块
在RN组件中,通过NativeModules调用上述原生模块:
jsx
import { View, Text, TouchableOpacity, StyleSheet, NativeModules } from 'react-native';
import { useState } from 'react';
// 获取自定义原生模块
const { DeviceModule } = NativeModules;
export default function CustomNativeModuleDemo() {
const [deviceModel, setDeviceModel] = useState('');
// 获取设备型号(异步调用)
const getModel = async () => {
try {
const model = await DeviceModule.getDeviceModel();
setDeviceModel(`设备型号:${model}`);
} catch (error) {
console.error('获取设备型号失败:', error);
}
};
// 显示自定义Toast
const showToast = () => {
DeviceModule.showCustomToast('这是原生Toast提示!');
};
return (
<View style={styles.container}>
<TouchableOpacity style={styles.btn} onPress={getModel}>
<Text style={styles.btnText}>获取设备型号</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.btn, { marginTop: 15 }]} onPress={showToast}>
<Text style={styles.btnText}>显示原生Toast</Text>
</TouchableOpacity>
{deviceModel ? <Text style={styles.modelText}>{deviceModel}</Text> : null}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f5f5f5',
},
btn: {
backgroundColor: '#0066cc',
paddingHorizontal: 30,
paddingVertical: 12,
borderRadius: 8,
},
btnText: {
color: '#fff',
fontSize: 16,
},
modelText: {
marginTop: 20,
fontSize: 16,
color: '#333',
},
});
3. 自定义原生模块(iOS端)
iOS端自定义原生模块的流程与Android类似,以下实现相同功能的iOS模块。
(1)创建iOS原生模块类
在iOS工程的[项目名]目录下,创建DeviceModule.h和DeviceModule.m文件:
objc
// DeviceModule.h
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(DeviceModule, NSObject)
// 暴露给JS的方法
RCT_EXTERN_METHOD(getDeviceModel:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(showCustomToast:(NSString *)message)
@end
objc
// DeviceModule.m
#import "DeviceModule.h"
#import <UIKit/UIKit.h>
@implementation DeviceModule
// 模块名称(需与头文件一致)
RCT_EXPORT_MODULE(DeviceModule);
// 获取设备型号
RCT_EXPORT_METHOD(getDeviceModel:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
@try {
// 获取设备型号
NSString *model = [UIDevice currentDevice].model;
resolve(model);
} @catch (NSException *e) {
reject(@"ERROR", e.reason, nil);
}
}
// 自定义Toast(iOS无原生Toast,需手动实现)
RCT_EXPORT_METHOD(showCustomToast:(NSString *)message) {
UIViewController *rootVC = [UIApplication sharedApplication].delegate.window.rootViewController;
// 创建Toast标签
UILabel *toast = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 40)];
toast.center = CGPointMake(rootVC.view.bounds.size.width / 2, rootVC.view.bounds.size.height * 0.8);
toast.backgroundColor = [UIColor blackColor];
toast.textColor = [UIColor whiteColor];
toast.textAlignment = NSTextAlignmentCenter;
toast.layer.cornerRadius = 20;
toast.clipsToBounds = YES;
toast.text = message;
[rootVC.view addSubview:toast];
// 自动消失
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[toast removeFromSuperview];
});
}
@end
(2)JS层调用(跨端统一)
iOS端无需额外注册,JS层代码与Android端完全一致,RN会自动识别iOS原生模块,实现跨端统一调用。
三、原生UI组件封装(进阶篇)
除了原生方法调用,RN还支持封装自定义原生UI组件(如Android的TextView、iOS的UILabel),实现高性能的原生UI展示。
1. Android原生UI组件封装
(1)创建原生ViewManager
java
package com.rndemo.views;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
import android.widget.TextView;
// 自定义原生TextView组件
public class CustomTextViewManager extends SimpleViewManager<TextView> {
public static final String REACT_CLASS = "CustomTextView";
@Override
public String getName() {
return REACT_CLASS;
}
// 创建原生View
@Override
protected TextView createViewInstance(ThemedReactContext reactContext) {
TextView textView = new TextView(reactContext);
textView.setTextSize(16);
return textView;
}
// 定义JS可设置的属性(如text、color)
@ReactProp(name = "text")
public void setText(TextView view, String text) {
view.setText(text);
}
@ReactProp(name = "textColor")
public void setTextColor(TextView view, String color) {
view.setTextColor(android.graphics.Color.parseColor(color));
}
}
(2)注册ViewManager
在CustomPackage的createViewManagers方法中注册:
java
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
List<ViewManager> managers = new ArrayList<>();
managers.add(new CustomTextViewManager());
return managers;
}
(3)JS层封装并使用原生UI组件
jsx
// components/CustomTextView.js
import { requireNativeComponent } from 'react-native';
// 引入原生UI组件
const CustomTextView = requireNativeComponent('CustomTextView');
export default CustomTextView;
jsx
// 页面中使用
import CustomTextView from '../components/CustomTextView';
export default function NativeUIComponentDemo() {
return (
<View style={styles.container}>
<CustomTextView
style={{ width: 200, height: 40 }}
text="原生TextView组件"
textColor="#ff6600"
/>
</View>
);
}
四、第三方原生SDK集成实战
实际开发中,常需集成第三方原生SDK(如高德地图、微信支付),以下以高德地图Android SDK为例,演示RN与第三方SDK的集成流程。
1. 集成高德地图Android SDK
(1)添加SDK依赖
在Android工程的build.gradle中添加依赖:
gradle
// 项目根目录build.gradle
allprojects {
repositories {
// 添加高德仓库
maven { url 'https://repo.amap.com/'}
}
}
// app/build.gradle
dependencies {
// 高德地图SDK
implementation 'com.amap.api:3dmap:9.8.0'
implementation 'com.amap.api:location:6.15.1'
}
(2)配置权限和Key
在AndroidManifest.xml中添加权限和高德Key:
xml
<!-- 权限声明 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- 高德Key配置 -->
<meta-data
android:name="com.amap.api.v2.apikey"
android:value="你的高德Key" />
(3)封装地图原生模块
创建AMapModule.java,实现地图初始化和定位功能,再通过JSBridge暴露给RN层调用。
(4)JS层调用地图SDK
通过NativeModules调用封装的地图模块,实现地图展示和定位功能。
2. 集成注意事项
- 权限申请 :原生SDK所需权限需在AndroidManifest/iOS Info.plist中声明,动态权限需在JS层通过
PermissionsAndroid/PermissionsIOS申请; - 生命周期管理:原生SDK的初始化和销毁需与RN组件生命周期绑定,避免内存泄漏;
- 版本兼容:需确保第三方SDK版本与RN版本、系统版本兼容,避免出现兼容性问题。
五、原生调用JS方法
除了JS调用原生,原生也可主动调用JS方法,实现原生事件向JS层的传递(如硬件传感器数据、推送消息)。
1. Android原生调用JS
java
// 获取ReactInstanceManager
ReactInstanceManager manager = getReactNativeHost().getReactInstanceManager();
ReactContext context = manager.getCurrentReactContext();
// 调用JS全局方法
if (context != null) {
context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("nativeEvent", "原生传递给JS的消息");
}
2. iOS原生调用JS
objc
// 获取bridge
RCTBridge *bridge = [self.bridge get];
// 调用JS方法
[bridge enqueueJSCall:@"RCTDeviceEventEmitter" method:@"emit" args:@[@"nativeEvent", @"原生传递给JS的消息"]];
3. JS层监听原生事件
jsx
import { NativeEventEmitter, NativeModules } from 'react-native';
// 创建事件发射器
const eventEmitter = new NativeEventEmitter(NativeModules.DeviceModule);
// 监听原生事件
const subscription = eventEmitter.addListener('nativeEvent', (message) => {
console.log('原生事件消息:', message);
});
// 组件卸载时取消监听
useEffect(() => {
return () => {
subscription.remove();
};
}, []);
六、小结与下一阶段预告
本文系统讲解了RN与原生模块的交互原理和实战方案,从内置原生模块调用,到自定义原生模块/UI组件开发,再到第三方SDK集成,你已具备RN与原生深度融合的开发能力,能够应对复杂的原生能力扩展需求。
下一篇文章《RN跨端适配与沉浸式体验:适配不同设备与系统》,将聚焦RN应用的多设备适配 和平台差异化处理,解决不同屏幕、不同系统的兼容性问题,打造更优质的跨端用户体验。
如果你在原生模块集成中遇到兼容性或通信问题,可随时留言,我会为你提供针对性的解决方案!