原生与 React Native 间的通信
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
在集成到现有应用指南和原生 UI 组件指南中,我们学习了如何在原生组件中嵌入 React Native 以及反向操作。当混合使用原生组件和 React Native 组件时,我们终将面临二者之间的通信需求。其他指南已提及部分实现方式,本文旨在系统梳理现有技术方案。
引言
React Native 的设计灵感源自 React,其数据流的基本理念也与之相似:单向数据流。我们维护组件层级结构,每个组件仅依赖其父组件和自身内部状态。这是通过属性(properties)实现的:数据以自顶向下的方式从父组件传递到子组件。若祖先组件需要依赖后代组件的状态,则应向下传递回调函数供后代更新祖先状态。
相同理念同样适用于 React Native。只要完全在框架内构建应用,我们可通过属性和回调驱动应用。但当混合使用 React Native 和原生组件时,就需要特定的跨语言机制来实现信息传递。
属性
属性是实现跨组件通信最直接的方式。因此我们需要实现双向属性传递:既支持从原生到 React Native,也支持从 React Native 到原生。
从原生传递属性到 React Native
您可在主 Activity 中提供自定义的 ReactActivityDelegate 实现,通过覆写 getLaunchOptions 方法返回包含目标属性的 Bundle 对象,从而向 React Native 应用传递属性。
- Java
- Kotlin
public class MainActivity extends ReactActivity {
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName()) {
@Override
protected Bundle getLaunchOptions() {
Bundle initialProperties = new Bundle();
ArrayList<String> imageList = new ArrayList<String>(Arrays.asList(
"https://dummyimage.com/600x400/ffffff/000000.png",
"https://dummyimage.com/600x400/000000/ffffff.png"
));
initialProperties.putStringArrayList("images", imageList);
return initialProperties;
}
};
}
}
class MainActivity : ReactActivity() {
override fun createReactActivityDelegate(): ReactActivityDelegate {
return object : ReactActivityDelegate(this, mainComponentName) {
override fun getLaunchOptions(): Bundle {
val imageList = arrayListOf("https://dummyimage.com/600x400/ffffff/000000.png", "https://dummyimage.com/600x400/000000/ffffff.png")
val initialProperties = Bundle().apply { putStringArrayList("images", imageList) }
return initialProperties
}
}
}
}
import React from 'react';
import {View, Image} from 'react-native';
export default class ImageBrowserApp extends React.Component {
renderImage(imgURI) {
return <Image source={{uri: imgURI}} />;
}
render() {
return <View>{this.props.images.map(this.renderImage)}</View>;
}
}
ReactRootView 提供可读写属性 appProperties。设置 appProperties 后,React Native 应用会使用新属性重新渲染。注意:仅当新旧属性存在差异时才会触发更新。
- Java
- Kotlin
Bundle updatedProps = mReactRootView.getAppProperties();
ArrayList<String> imageList = new ArrayList<String>(Arrays.asList(
"https://dummyimage.com/600x400/ff0000/000000.png",
"https://dummyimage.com/600x400/ffffff/ff0000.png"
));
updatedProps.putStringArrayList("images", imageList);
mReactRootView.setAppProperties(updatedProps);
var updatedProps: Bundle = reactRootView.getAppProperties()
var imageList = arrayListOf("https://dummyimage.com/600x400/ff0000/000000.png", "https://dummyimage.com/600x400/ffffff/ff0000.png")
属性可随时更新,但必须在主线程执行更新操作。获取操作可在任意线程执行。
目前不支持局部属性更新。建议自行构建封装器实现此功能。
当前顶层 RN 组件的 JS 函数 componentWillUpdateProps 在属性更新后不会被调用。但可通过 componentDidMount 函数访问新属性。
从 React Native 传递属性到原生
原生组件属性的暴露机制已在此文详述。简言之:需在原生端通过 @ReactProp 注解声明 setter 方法,之后在 React Native 中即可像操作普通 RN 组件一样使用这些属性。
属性通信的局限
跨语言属性通信的主要缺陷是不支持回调函数,导致无法处理自底向上的数据绑定。例如:当需要通过 JS 操作移除原生父视图中的 RN 子视图时,属性机制无法实现此需求——因为信息需要自底向上传递。
尽管存在跨语言回调机制(参见此处),但其设计初衷并非作为属性传递。该机制主要用于从 JS 触发原生操作,并在 JS 中处理操作结果。
其他跨语言交互方式(事件与原生模块)
正如前一章所述,使用属性存在一些局限性。有时属性不足以驱动应用程序的逻辑,我们需要更灵活的解决方案。本章将介绍 React Native 中可用的其他通信技术,这些技术既可用于内部通信(RN 中 JS 与原生层之间),也可用于外部通信(RN 与应用程序"纯原生"部分之间)。
React Native 支持跨语言函数调用。您可以从 JS 执行自定义原生代码,反之亦然。但根据操作端不同,实现方式有所差异:在原生端我们使用事件机制调度 JS 的处理函数执行,而在 React Native 端则直接调用原生模块导出的方法。
从原生端调用 React Native 函数(事件)
事件机制在这篇文章中有详细说明。请注意事件处理在独立线程执行,因此无法保证确切的执行时机。
事件非常强大,因为它允许我们在不需要组件引用的情况下修改 React Native 组件。但使用时需注意以下陷阱:
-
事件可从任意位置发送,可能导致项目中出现面条式依赖
-
事件共享命名空间,可能发生名称冲突。冲突无法静态检测,难以调试
-
当使用多个相同 React Native 组件实例时,若需在事件中区分它们,通常需要引入标识符随事件传递(可使用原生视图的
reactTag作为标识符)
从 React Native 调用原生函数(原生模块)
原生模块是可供 JavaScript 调用的 Java/Kotlin 类,每个模块在每个 JavaScript 桥接器中仅实例化一次。它们可以向 React Native 暴露任意函数和常量,详细实现请参阅这篇文章。
所有原生模块共享同一命名空间,创建新模块时需警惕命名冲突