React Native 与原生交互 & 原生组件开发
1.RN Bridge 架构演进
┌─────────────────────────────────────────┐
│ JavaScript (React) │
├─────────────────────────────────────────┤
│ Shadow Tree (Yoga Layout) │
├─────────────────────────────────────────┤
│ Bridge / JSI (新架构) │ <- 通信层
├─────────────────────────────────────────┤
│ Native Modules (Java/Kotlin) │
├─────────────────────────────────────────┤
│ Android Platform │
└─────────────────────────────────────────┘
2.原生模块开发 (Native Module)
2.1 创建原生模块 - Android
<kotlin>
// ToastModule.ktpackage com.myappimport android.widget.Toastimport com.facebook.react.bridge.*import com.facebook.react.module.annotations.ReactModule@ReactModule(name = ToastModule.NAME)class ToastModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { companion object { const val NAME = "ToastModule" } override fun getName() = NAME // 同步方法 @ReactMethod fun show(message: String, duration: Int) { UiThreadUtil.runOnUiThread { Toast.makeText( reactApplicationContext, message, if (duration == 0) Toast.LENGTH_SHORT else Toast.LENGTH_LONG ).show() } } // 异步方法 - 使用 Promise @ReactMethod fun fetchUserData(userId: String, promise: Promise) { try { // 模拟异步操作 Thread { Thread.sleep(1000) val userData = WritableNativeMap().apply { putString("id", userId) putString("name", "John Doe") putInt("age", 25) } promise.resolve(userData) }.start() } catch (e: Exception) { promise.reject("FETCH_ERROR", e.message, e) } } // 异步方法 - 使用 Callback @ReactMethod fun getData(callback: Callback) { try { val data = "Native Data" callback.invoke(null, data) // 第一个参数是 error } catch (e: Exception) { callback.invoke(e.message, null) } } // 发送事件到 JS private fun sendEvent(eventName: String, params: WritableMap?) { reactApplicationContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit(eventName, params) } @ReactMethod fun startLocationTracking() { // 持续发送位置更新 locationManager.requestLocationUpdates { location -> val params = WritableNativeMap().apply { putDouble("latitude", location.latitude) putDouble("longitude", location.longitude) } sendEvent("onLocationUpdate", params) } } // 导出常量 override fun getConstants(): Map<String, Any> { return mapOf( "SHORT_DURATION" to Toast.LENGTH_SHORT, "LONG_DURATION" to Toast.LENGTH_LONG ) }}
2.2 注册原生模块
<kotlin>
// MyAppPackage.ktclass MyAppPackage : ReactPackage { override fun createNativeModules( reactContext: ReactApplicationContext ): List<NativeModule> { return listOf( ToastModule(reactContext), CameraModule(reactContext), // 添加其他模块 ) } override fun createViewManagers( reactContext: ReactApplicationContext ): List<ViewManager<*, *>> { return listOf( CustomVideoViewManager(), // 添加其他视图管理器 ) }}// MainApplication.ktclass MainApplication : Application(), ReactApplication { override val reactNativeHost: ReactNativeHost = object : DefaultReactNativeHost(this) { override fun getPackages(): List<ReactPackage> { return PackageList(this).packages.apply { add(MyAppPackage()) // 添加自定义包 } } }}
2.3 JavaScript 端使用
<javascript>
// ToastModule.jsimport { NativeModules, NativeEventEmitter } from 'react-native';const { ToastModule } = NativeModules;// 使用同步方法ToastModule.show('Hello from Native!', ToastModule.SHORT_DURATION);// 使用 Promiseconst fetchUser = async (userId) => { try { const userData = await ToastModule.fetchUserData(userId); console.log('User data:', userData); } catch (error) { console.error('Error fetching user:', error); }};// 使用 CallbackToastModule.getData((error, data) => { if (error) { console.error(error); } else { console.log('Data:', data); }});// 监听事件const eventEmitter = new NativeEventEmitter(ToastModule);const subscription = eventEmitter.addListener('onLocationUpdate', (location) => { console.log('Location:', location);});// 记得取消订阅subscription.remove();
3.原生 UI 组件开发
3.1 创建自定义视图 - Android
<kotlin>
// CustomVideoView.ktclass CustomVideoView(context: Context) : VideoView(context) { var onPlaybackComplete: (() -> Unit)? = null init { setOnCompletionListener { onPlaybackComplete?.invoke() } } fun setVideoPath(path: String) { super.setVideoPath(path) start() } fun pauseVideo() { if (isPlaying) { pause() } } fun resumeVideo() { if (!isPlaying) { start() } }}
3.2 创建 ViewManager
<kotlin>
// CustomVideoViewManager.kt@ReactModule(name = CustomVideoViewManager.REACT_CLASS)class CustomVideoViewManager : SimpleViewManager<CustomVideoView>() { companion object { const val REACT_CLASS = "CustomVideoView" const val COMMAND_PLAY = 1 const val COMMAND_PAUSE = 2 } override fun getName() = REACT_CLASS override fun createViewInstance(reactContext: ThemedReactContext): CustomVideoView { return CustomVideoView(reactContext) } // Props 设置 @ReactProp(name = "videoPath") fun setVideoPath(view: CustomVideoView, path: String?) { path?.let { view.setVideoPath(it) } } @ReactProp(name = "autoPlay", defaultBoolean = true) fun setAutoPlay(view: CustomVideoView, autoPlay: Boolean) { if (autoPlay) { view.start() } } @ReactProp(name = "volume", defaultFloat = 1.0f) fun setVolume(view: CustomVideoView, volume: Float) { // 设置音量 } // 导出事件 override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> { return mapOf( "onPlaybackComplete" to mapOf( "registrationName" to "onPlaybackComplete" ), "onPlaybackError" to mapOf( "registrationName" to "onPlaybackError" ) ) } // 处理命令 override fun getCommandsMap(): Map<String, Int> { return mapOf( "play" to COMMAND_PLAY, "pause" to COMMAND_PAUSE ) } override fun receiveCommand( view: CustomVideoView, commandId: String, args: ReadableArray? ) { when (commandId.toInt()) { COMMAND_PLAY -> view.resumeVideo() COMMAND_PAUSE -> view.pauseVideo() } } // 发送事件到 JS private fun sendEvent(view: CustomVideoView, eventName: String, data: WritableMap?) { val reactContext = view.context as ReactContext reactContext.getJSModule(RCTEventEmitter::class.java) .receiveEvent(view.id, eventName, data) }}
3.3 JavaScript 端包装组件
<javascript>
// CustomVideoView.jsimport React, { useRef, useImperativeHandle, forwardRef } from 'react';import { requireNativeComponent, UIManager, findNodeHandle, ViewStyle } from 'react-native';const NativeCustomVideoView = requireNativeComponent('CustomVideoView');const CustomVideoView = forwardRef((props, ref) => { const nativeRef = useRef(null); useImperativeHandle(ref, () => ({ play: () => { UIManager.dispatchViewManagerCommand( findNodeHandle(nativeRef.current), UIManager.CustomVideoView.Commands.play, [] ); }, pause: () => { UIManager.dispatchViewManagerCommand( findNodeHandle(nativeRef.current), UIManager.CustomVideoView.Commands.pause, [] ); } })); const handlePlaybackComplete = (event) => { if (props.onPlaybackComplete) { props.onPlaybackComplete(event.nativeEvent); } }; return ( <NativeCustomVideoView ref={nativeRef} {...props} onPlaybackComplete={handlePlaybackComplete} /> );});// 使用组件export default function VideoPlayer() { const videoRef = useRef(null); return ( <CustomVideoView ref={videoRef} videoPath="/path/to/video.mp4" autoPlay={true} volume={0.5} style={{ width: '100%', height: 200 }} onPlaybackComplete={() => console.log('Video completed')} /> );}
4.复杂交互示例 - 相机模块
<kotlin>
// CameraModule.kt@ReactModule(name = CameraModule.NAME)class CameraModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext), ActivityEventListener { companion object { const val NAME = "CameraModule" const val REQUEST_IMAGE_CAPTURE = 1 } private var imagePromise: Promise? = null init { reactContext.addActivityEventListener(this) } override fun getName() = NAME @ReactMethod fun takePicture(options: ReadableMap, promise: Promise) { val currentActivity = currentActivity if (currentActivity == null) { promise.reject("E_ACTIVITY_DOES_NOT_EXIST", "Activity doesn't exist") return } imagePromise = promise val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) if (takePictureIntent.resolveActivity(reactContext.packageManager) != null) { currentActivity.startActivityForResult( takePictureIntent, REQUEST_IMAGE_CAPTURE ) } else { promise.reject("E_CAMERA_NOT_AVAILABLE", "Camera not available") } } override fun onActivityResult( activity: Activity?, requestCode: Int, resultCode: Int, data: Intent? ) { if (requestCode == REQUEST_IMAGE_CAPTURE) { if (resultCode == Activity.RESULT_OK) { val imageBitmap = data?.extras?.get("data") as? Bitmap if (imageBitmap != null) { // 转换为 Base64 val outputStream = ByteArrayOutputStream() imageBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream) val base64 = Base64.encodeToString( outputStream.toByteArray(), Base64.DEFAULT ) val result = WritableNativeMap().apply { putString("base64", base64) putInt("width", imageBitmap.width) putInt("height", imageBitmap.height) } imagePromise?.resolve(result) } else { imagePromise?.reject("E_NO_IMAGE_DATA", "No image data") } } else { imagePromise?.reject("E_PICKER_CANCELLED", "User cancelled") } imagePromise = null } }}
5.新架构 - Turbo Modules 和 Fabric
5.1 Turbo Module (使用 JSI)
<cpp>
// TurboModule 示例 - C++#include <jsi/jsi.h>using namespace facebook;class NativeSampleModule : public jsi::HostObject {public: jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& name) override { auto methodName = name.utf8(runtime); if (methodName == "multiply") { return jsi::Function::createFromHostFunction( runtime, name, 2, // 参数数量 [](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value { if (count != 2) { throw jsi::JSError(runtime, "multiply expects 2 arguments"); } double a = arguments[0].asNumber(); double b = arguments[1].asNumber(); return jsi::Value(a * b); } ); } return jsi::Value::undefined(); }};
5.2 Fabric 组件
<kotlin>
// Fabric 组件示例@ReactModule(name = CustomViewManager.REACT_CLASS)class CustomViewManager : ViewGroupManager<CustomView>() { companion object { const val REACT_CLASS = "CustomView" } override fun getName() = REACT_CLASS override fun createViewInstance( reactContext: ThemedReactContext ) = CustomView(reactContext) // Fabric 特定的 Shadow Node override fun createShadowNodeInstance(): ReactShadowNode { return CustomViewShadowNode() } override fun getShadowNodeClass() = CustomViewShadowNode::class.java}
6.性能优化建议
<kotlin>
// 1. 批量操作@ReactMethodfun batchOperation(operations: ReadableArray, promise: Promise) { val results = WritableNativeArray() for (i in 0 until operations.size()) { val op = operations.getMap(i) // 处理操作 results.pushMap(processOperation(op)) } promise.resolve(results)}// 2. 使用线程池private val executor = Executors.newFixedThreadPool(4)@ReactMethodfun heavyOperation(data: String, promise: Promise) { executor.execute { try { val result = performHeavyWork(data) promise.resolve(result) } catch (e: Exception) { promise.reject("ERROR", e.message) } }}// 3. 避免频繁的 Bridge 调用class OptimizedModule { private val cache = mutableMapOf<String, Any>() @ReactMethod(isBlockingSynchronousMethod = true) fun getCachedValue(key: String): Any? { return cache[key] }}
总结
React Native 原生交互的关键点:
Bridge/JSI是核心通信机制Native Module处理逻辑功能ViewManager管理原生 UI 组件事件系统实现双向通信新架构(Turbo Modules & Fabric) 提供更好的性能
相比 Flutter,RN 的原生交互更加灵活,可以直接复用现有的原生代码,但性能开销相对较大。