原生与 React Native 之间的通信
在 集成现有应用指南 和 原生 UI 组件指南 中,我们学习了如何将 React Native 嵌入到原生组件中,以及反之亦然。当我们混合使用原生组件和 React Native 组件时,最终会需要在这两个世界之间进行通信。实现这一点的一些方法已经在其他指南中提到过。本文总结了可用的技术。
简介
React Native 受 React 的启发,因此信息流的基本思想是相似的。React 中的信息流是单向的。我们维护一个组件层级,其中每个组件仅依赖于其父组件和自身的内部状态。我们通过 props 来实现这一点:数据以自顶向下的方式从父组件传递到子组件。如果祖先组件依赖于其后代组件的状态,则应该将一个回调函数传递下去,供后代组件使用来更新祖先组件。
同样的理念也适用于 React Native。只要我们在框架内纯粹地构建应用程序,就可以通过 props 和回调函数来驱动我们的应用。但是,当我们混合使用 React Native 和原生组件时,我们需要一些特定的、跨语言的机制,以允许我们在它们之间传递信息。
属性
Props 是最直接的跨组件通信方式。因此,我们需要一种方法,既可以从原生传递 props 到 React Native,也可以从 React Native 传递 props 到原生。
从原生向 React Native 传递 props
您可以通过在主 Activity 中提供 `ReactActivityDelegate` 的自定义实现来向下传递 props 给 React Native 应用。此实现应重写 `getLaunchOptions` 以返回一个包含所需 props 的 `Bundle`。
- 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 应用会使用新的 props 重新渲染。只有当新的更新后的 props 与之前的不同时,才会执行更新。
- 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")
随时更新 props 都可以。但是,更新必须在主线程上执行。您可以在任何线程上使用 getter。
没有办法一次只更新几个 props。我们建议您自己构建一个包装器来实现这一点。
目前,顶级 RN 组件的 JS 函数 `componentWillUpdateProps` 在 props 更新后不会被调用。但是,您可以在 `componentDidMount` 函数中访问新的 props。
从 React Native 向原生传递 props
暴露原生组件 props 的问题已在 本文 中详细介绍。简而言之,需要在 JavaScript 中反射的 props 需要暴露为带有 `@ReactProp` 注解的 setter 方法,然后在 React Native 中像使用普通 React Native 组件一样使用它们。
Props 的限制
跨语言 props 的主要缺点是它们不支持回调函数,而回调函数可以让我们处理自下而上的数据绑定。想象一下,您有一个小的 RN 视图,您希望它由于 JS 操作而被从原生父视图中移除。使用 props 无法做到这一点,因为信息需要自下而上地传递。
尽管我们有一种跨语言回调的变体(在此处描述),但这些回调函数并不总是我们所需要的。主要问题是它们并不打算作为 props 传递。相反,这种机制允许我们从 JS 触发原生操作,并在 JS 中处理该操作的结果。
其他跨语言交互方式(事件和原生模块)
如上一章所述,使用 props 存在一些限制。有时 props 不足以驱动我们应用程序的逻辑,我们需要一个更灵活的解决方案。本章介绍 React Native 中可用的其他通信技术。它们可以用于内部通信(RN 的 JS 和原生层之间)以及外部通信(RN 和您应用程序的“纯原生”部分之间)。
React Native 使您能够执行跨语言函数调用。您可以从 JS 执行自定义原生代码,反之亦然。不幸的是,根据我们工作的 Side,我们以不同的方式实现相同的目标。对于原生,我们使用事件机制来调度 JS 中处理函数的执行,而对于 React Native,我们直接调用原生模块导出的方法。
从原生调用 React Native 函数(事件)
事件的详细描述请参见 本文。请注意,使用事件无法保证执行时间,因为事件是在单独的线程上处理的。
事件功能强大,因为它们允许我们更改 React Native 组件而无需引用它们。但是,在使用它们时可能会遇到一些陷阱。
- 由于事件可以从任何地方发送,它们可能会在您的项目中引入“意大利面条式”的依赖关系。
- 事件共享命名空间,这意味着您可能会遇到一些名称冲突。冲突不会被静态检测到,这使得它们难以调试。
- 如果您使用多个相同的 React Native 组件实例,并且希望从事件的角度区分它们,您可能需要引入标识符并将它们与事件一起传递(您可以使用原生视图的 `reactTag` 作为标识符)。
从 React Native 调用原生函数(原生模块)
原生模块是可在 JS 中使用的 Java/Kotlin 类。通常,每个模块会在每个 JS Bridge 上创建一个实例。它们可以向 React Native 导出任意函数和常量。这些在 本文 中已详细介绍。
所有原生模块共享相同的命名空间。创建新模块时请注意名称冲突。