跳到主要内容

原生与 React Native 之间的通信

与现有应用集成指南原生 UI 组件指南 中,我们学习了如何将 React Native 嵌入到原生组件中,反之亦然。当我们混合原生和 React Native 组件时,最终会发现需要在这两个世界之间进行通信。其他指南中已经提到了一些实现方法。本文总结了可用的技术。

引言

React Native 的灵感来源于 React,所以信息流的基本思想是相似的。React 中的流是单向的。我们维护一个组件层次结构,其中每个组件只依赖于其父组件和它自己的内部状态。我们通过属性来做到这一点:数据以自上而下的方式从父组件传递给其子组件。如果祖先组件依赖于其后代组件的状态,则应向下传递一个回调函数,供后代组件用于更新祖先组件。

同样的概念也适用于 React Native。只要我们完全在框架内部构建应用程序,就可以通过属性和回调函数来驱动应用程序。但是,当我们混合使用 React Native 和原生组件时,我们需要一些特定的跨语言机制,以允许我们在它们之间传递信息。

属性

属性是组件间通信最直接的方式。因此,我们需要一种方法,既能将属性从原生传递给 React Native,也能将属性从 React Native 传递给原生。

从原生传递属性到 React Native

你可以通过在主活动中提供 ReactActivityDelegate 的自定义实现,将属性向下传递给 React Native 应用。此实现应覆盖 getLaunchOptions 以返回一个包含所需属性的 Bundle

java
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;
}
};
}
}
tsx
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
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);

随时更新属性是可以的。但是,更新必须在主线程上执行。你可以在任何线程上使用 getter。

目前无法一次只更新少数几个属性。我们建议你将其构建到自己的包装器中。

注意: 目前,顶级 RN 组件的 JS 函数 componentWillUpdateProps 在属性更新后不会被调用。但是,你可以在 componentDidMount 函数中访问新的属性。

从 React Native 传递属性到原生

原生组件属性的暴露问题在 本文 中有详细介绍。简而言之,需要在 JavaScript 中反映的属性需要以带有 @ReactProp 注解的 setter 方法暴露,然后在 React Native 中使用它们,就像该组件是一个普通的 React Native 组件一样。

属性的局限性

跨语言属性的主要缺点是它们不支持回调,而回调可以让我们处理自下而上的数据绑定。想象一下,你有一个小的 RN 视图,由于 JS 操作,你希望将其从原生父视图中移除。通过属性无法做到这一点,因为信息需要自下而上地传递。

尽管我们有一种跨语言回调的形式(在此处描述),但这些回调并非总是我们所需的。主要问题是它们不打算作为属性传递。相反,这种机制允许我们从 JS 触发原生操作,并在 JS 中处理该操作的结果。

其他跨语言交互方式(事件和原生模块)

如前一章所述,使用属性存在一些局限性。有时属性不足以驱动我们应用程序的逻辑,我们需要一个提供更大灵活性的解决方案。本章介绍了 React Native 中可用的其他通信技术。它们可用于内部通信(RN 中的 JS 和原生层之间)以及外部通信(RN 和应用程序的“纯原生”部分之间)。

React Native 使你能够执行跨语言函数调用。你可以从 JS 执行自定义原生代码,反之亦然。不幸的是,根据我们工作的方面,我们以不同的方式实现相同的目标。对于原生,我们使用事件机制来调度 JS 中处理函数的执行,而对于 React Native,我们直接调用原生模块导出的方法。

从原生调用 React Native 函数(事件)

事件在 本文 中有详细描述。请注意,使用事件无法保证执行时间,因为事件是在单独的线程上处理的。

事件功能强大,因为它们允许我们无需引用即可更改 React Native 组件。但是,使用它们时可能会遇到一些陷阱

  • 由于事件可以从任何地方发送,它们可能会在你的项目中引入意大利面条式的依赖关系。
  • 事件共享命名空间,这意味着你可能会遇到一些名称冲突。冲突不会静态检测到,这使得它们难以调试。
  • 如果你使用同一个 React Native 组件的多个实例,并且希望从事件的角度区分它们,你可能需要引入标识符并随事件一起传递(你可以使用原生视图的 reactTag 作为标识符)。

从 React Native 调用原生函数(原生模块)

原生模块是 Java/Kotlin 类,可在 JS 中使用。通常,每个 JS 桥接器会创建一个模块实例。它们可以将任意函数和常量导出到 React Native。它们已在 本文 中详细介绍。

警告: 所有原生模块共享相同的命名空间。创建新模块时请注意名称冲突。