深入解析:RN原生模块交互:打通JS与原生的桥梁

RN原生模块交互:打通JS与原生的桥梁

在前几篇文章中,我们掌握了RN的基础组件、路由管理和状态管理,能够开发常规的跨端应用。但在实际业务中,RN的JS层能力有限,很多场景(如调用手机传感器、集成原生SDK、自定义高性能UI)需要依赖原生代码。本文作为RN体系化专栏的第四篇,将深入讲解RN与原生模块的交互原理Android/iOS原生模块开发第三方SDK集成,帮助你打通JS与原生的通信壁垒,实现RN能力的深度扩展。

一、RN与原生通信原理:JSBridge

RN的跨端能力本质是通过JSBridge实现JS层与原生层的双向通信,其核心是一个异步消息传递机制,工作流程可分为三步:

  1. JS层发起调用:JS通过NativeModules调用原生模块方法,参数经序列化后通过JSBridge发送给原生层;
  2. 原生层处理请求:原生层接收消息后,解析参数并执行对应逻辑,完成后将结果序列化返回;
  3. JS层接收结果:JSBridge将原生返回的结果传递给JS层回调函数,完成一次通信。

RN的原生模块交互分为两种模式:

  • JS调用原生:如调用原生相机、获取设备信息;
  • 原生调用JS:如原生监听硬件事件后通知JS层(如电量变化、推送消息)。

二、JS调用原生模块(基础篇)

RN已内置部分原生模块(如AlertAsyncStorage),可直接通过react-nativeAPI调用,这是最基础的原生交互方式。

1. 调用RN内置原生模块

Alert(弹窗)和Linking(打开链接)为例,无需编写原生代码即可直接使用:

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 (
    
      
        显示原生弹窗
      
      
        打开浏览器
      
    
  );
}
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

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中:

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:

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调用上述原生模块:

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 (
    
      
        获取设备型号
      
      
        显示原生Toast
      
      {deviceModel ? {deviceModel} : null}
    
  );
}
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.hDeviceModule.m文件:

// 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
// 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
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

CustomPackagecreateViewManagers方法中注册:

@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
  List<ViewManager> managers = new ArrayList<>();
    managers.add(new CustomTextViewManager());
    return managers;
    }
(3)JS层封装并使用原生UI组件
// components/CustomTextView.js
import { requireNativeComponent } from 'react-native';
// 引入原生UI组件
const CustomTextView = requireNativeComponent('CustomTextView');
export default CustomTextView;
// 页面中使用
import CustomTextView from '../components/CustomTextView';
export default function NativeUIComponentDemo() {
  return (
    
      
    
  );
}

四、第三方原生SDK集成实战

实际开发中,常需集成第三方原生SDK(如高德地图、微信支付),以下以高德地图Android SDK为例,演示RN与第三方SDK的集成流程。

1. 集成高德地图Android SDK

(1)添加SDK依赖

在Android工程的build.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:

<!-- 权限声明 -->
  <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

// 获取ReactInstanceManager
ReactInstanceManager manager = getReactNativeHost().getReactInstanceManager();
ReactContext context = manager.getCurrentReactContext();
// 调用JS全局方法
if (context != null) {
context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("nativeEvent", "原生传递给JS的消息");
}

2. iOS原生调用JS

// 获取bridge
RCTBridge *bridge = [self.bridge get];
// 调用JS方法
[bridge enqueueJSCall:@"RCTDeviceEventEmitter" method:@"emit" args:@[@"nativeEvent", @"原生传递给JS的消息"]];

3. JS层监听原生事件

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应用的多设备适配平台差异化处理,解决不同屏幕、不同系统的兼容性问题,打造更优质的跨端用户体验。

如果你在原生模块集成中遇到兼容性或通信问题,可随时留言,我会为你提供针对性的解决方案!

posted @ 2026-01-08 20:25  yangykaifa  阅读(8)  评论(0)    收藏  举报