跳到主要内容

原生与 React Native 之间的通信

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

简介

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

相同的概念也适用于 React Native。 只要我们在框架内纯粹地构建应用程序,我们就可以使用属性和回调来驱动我们的应用程序。 但是,当我们将 React Native 组件和原生组件混合使用时,我们需要一些特定的、跨语言的机制,以便在这两者之间传递信息。

属性

属性是跨组件通信最直接的方式。 因此,我们需要一种方法来传递属性,既可以从原生传递到 React Native,也可以从 React Native 传递到原生。

从原生向 React Native 传递属性

你可以通过在你的主 Activity 中提供 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 调用原生函数(原生模块)

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

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