Communication between native and React Native
Communication between native and React Native
需要本机代码的项目
此页面仅适用于react-native init
使用Create React Native App 制作的或使用此类应用程序弹出的项目。有关弹出的更多信息,请参阅创建React Native App存储库的指南。
在与现有应用程序指南和本地用户界面组件指南集成中,我们将学习如何将本机组件嵌入React Native,反之亦然。当我们混合native和React Native组件时,我们最终会发现需要在这两个世界之间进行通信。其他指南中已经提到了一些实现这些方法的方法。本文总结了可用技术。
介绍
React Native受React的启发,所以信息流的基本思想是相似的。React中的流程是单向的。我们维护一个组件的层次结构,其中每个组件只依赖于它的父节点和它自己的内部状态。我们用属性来做到这一点:数据以自顶向下的方式从父项传递给子项。如果祖先组件依赖于它的后代的状态,那么应该传递一个回调供后裔用来更新祖先。
相同的概念适用于React Native。只要我们纯粹在框架内构建应用程序,就可以通过属性和回调来驱动我们的应用程序。但是,当我们将React Native与本地组件混合在一起时,我们需要一些特殊的跨语言机制,允许我们在它们之间传递信息。
属性
属性是交叉组件通信的最简单方式。所以我们需要一种方法来将属性从本地传递给React Native,并从React Native传递给本地。
将属性从本机传递给React Native
为了在本地组件中嵌入React Native视图,我们使用RCTRootView
。RCTRootView
是一个UIView
拥有React Native应用程序的应用程序。它还提供了本地端和托管应用程序之间的接口。
RCTRootView
有一个初始化程序,允许您将任意属性传递给React Native应用程序。该initialProperties
参数必须是一个实例NSDictionary
。字典在内部转换为顶级JS组件可以引用的JSON对象。
NSArray *imageList = @[@"http://foo.com/bar1.png",
@"http://foo.com/bar2.png"];
NSDictionary *props = @{@"images" : imageList};
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"ImageBrowserApp"
initialProperties:props];
'use strict';
import React from 'react';
import {
AppRegistry,
View,
Image
} from 'react-native';
class ImageBrowserApp extends React.Component {
renderImage(imgURI) {
return (
<Image source={{uri: imgURI}} />
}
render() {
return (
<View>
{this.props.images.map(this.renderImage)}
</View>
}
}
AppRegistry.registerComponent('AwesomeProject', () => ImageBrowserApp
RCTRootView
还提供了一个读写属性appProperties
。之后appProperties
被设置,阵营本地应用程序进行重新渲染与新的属性。仅当新的更新属性与先前的属性不同时才执行更新。
NSArray *imageList = @[@"http://foo.com/bar3.png",
@"http://foo.com/bar4.png"];
rootView.appProperties = @{@"images" : imageList};
随时更新属性是很好的。但是,更新必须在主线程上执行。你在任何线程上使用getter。
没有办法一次只更新一些属性。我们建议您将其构建到自己的包装中。
注意:
目前,在更新道具后,JS功能componentWillReceiveProps
和componentWillUpdateProps
顶级RN组件不会被调用。但是,您可以在componentWillMount
功能中访问新的道具。
将属性从React Native传递给本地
本文详细介绍了本机组件的问题暴露问题。简而言之,RCT_CUSTOM_VIEW_PROPERTY
在您的自定义本地组件中使用宏导出属性,然后在React Native中使用它们,就好像组件是普通的React Native组件。
属性的限制
跨语言属性的主要缺点是它们不支持回调,这将允许我们处理自下而上的数据绑定。想象一下,您有一个小的RN视图,您想从JS动作的结果中将其从本地父视图中删除。道具无法做到这一点,因为信息需要自下而上。
尽管我们有跨语言回调的风格(这里描述),但这些回调并不总是我们需要的东西。主要问题是它们不打算作为属性传递。相反,这种机制允许我们从JS中触发本地操作,并处理JS中该操作的结果。
跨语言交互的其他方式(事件和本机模块)
如前一章所述,使用属性会带来一些限制。有时,属性不足以驱动我们的应用程序的逻辑,我们需要一个提供更多灵活性的解决方案。本章介绍React Native中可用的其他通信技术。它们可以用于内部通信(在RN和本地层之间)以及外部通信(在RN和应用程序的“纯本地”部分之间)。
React Native允许您执行跨语言函数调用。你可以从JS执行自定义的本地代码,反之亦然。不幸的是,取决于我们正在努力的方面,我们以不同的方式实现相同的目标。对于本机 - 我们使用事件机制来安排JS中处理函数的执行,而对于React Native,我们直接调用由本地模块导出的方法。
从本机(事件)调用React Native函数
本文将详细介绍事件。请注意,使用事件不能保证执行时间,因为事件在单独的线程上处理。
事件功能强大,因为它们允许我们更改React Native组件,而无需引用它们。但是,在使用它们时会遇到一些陷阱:
- 由于事件可以从任何地方发送,他们可以将意大利面风格的依赖项引入到您的项目中。
- 事件共享名称空间,这意味着您可能会遇到一些名称冲突。碰撞不会被静态检测到,这使得它们很难调试。
- 如果您使用相同React Native组件的多个实例,并且希望将它们与事件的角度区分开来,则可能需要引入标识符并将它们与事件一起传递(可以使用本地视图
reactTag
作为标识符)。
我们在将本机嵌入到React Native中时使用的常见模式是让本地组件的RCTViewManager成为视图的委托,并通过桥将事件发送回JavaScript。这将相关的事件调用保存在一个地方。
从React Native调用本地函数(本机模块)
原生模块是JS中可用的Objective-C类。通常每个JS桥都创建一个模块的一个实例。他们可以将任意函数和常量导出到React Native。这篇文章详细介绍了它们。
本地模块是单例的事实限制了嵌入上下文中的机制。假设我们在本地视图中嵌入了React Native组件,并且我们想更新本地父视图。使用本机模块机制,我们将导出一个函数,该函数不仅需要预期的参数,而且还包含父本机视图的标识符。该标识符将用于检索对父视图的引用以进行更新。也就是说,我们需要在模块中保留从标识符到本地视图的映射。
尽管此解决方案非常复杂,但它用于RCTUIManager
管理所有React Native视图的内部React Native类。
本地模块也可用于将现有的本地库展示给JS。该地理位置图书馆的理念是一个活生生的例子。
警告
:所有本地模块共享相同的名称空间。注意创建新名称时的名称冲突。
布局计算流程
在集成本机和React Native时,我们还需要一种方法来整合两种不同的布局系统。本节涵盖常见的布局问题,并提供解决这些问题的机制的简要说明。
嵌入在React Native中的本地组件的布局
本文将介绍这种情况。基本上,由于我们所有的原生反应视图都是子类UIView
,因此大多数样式和大小属性都可以像您期望的那样工作。
嵌入在本机中的React Native组件的布局
以固定大小回应原生内容
最简单的情况是当我们有一个固定大小的React Native应用程序时,它是本机端已知的。特别是,全屏React Native视图属于这种情况。如果我们想要一个更小的根视图,我们可以明确地设置RCTRootView的框架。
例如,要制作一个RN应用程序200(逻辑)像素高,并且托管视图的宽度很宽,我们可以这样做:
// SomeViewController.m
- (void)viewDidLoad
{
[...]
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:appName
initialProperties:props];
rootView.frame = CGRectMake(0, 0, self.view.width, 200
[self.view addSubview:rootView];
}
当我们有一个固定大小的根视图时,我们需要尊重它在JS方面的界限。换句话说,我们需要确保React Native内容可以包含在固定大小的根视图中。确保这一点的最简单方法是使用flexbox布局。如果使用绝对定位,并且React组件在根视图的边界外可见,则会与本机视图重叠,导致某些功能出现意外情况。例如,'TouchableHighlight'不会在根视图的界限之外突出显示您的触摸。
通过重新设置其帧属性动态更新根视图的大小是完全正确的。React Native会处理内容的布局。
以灵活的大小回应原生内容
在某些情况下,我们希望呈现最初未知大小的内容。比方说,大小将在JS中动态定义。我们有两个解决这个问题的方法。
- 你可以将你的React Native视图包装在一个
ScrollView
组件中。这可以确保您的内容始终可用,并且不会与本机视图重叠。
- React Native允许您在JS中确定RN应用程序的大小并将其提供给托管所有者
RCTRootView
。然后,所有者负责重新布局子视图并保持UI一致。我们RCTRootView
通过灵活性模式实现这一目标。
RCTRootView
支持4种不同尺寸的灵活模式:
// RCTRootView.h
typedef NS_ENUM(NSInteger, RCTRootViewSizeFlexibility) {
RCTRootViewSizeFlexibilityNone = 0,
RCTRootViewSizeFlexibilityWidth,
RCTRootViewSizeFlexibilityHeight,
RCTRootViewSizeFlexibilityWidthAndHeight,
};
RCTRootViewSizeFlexibilityNone
是默认值,这使得固定根视图的大小(但它仍然可以更新setFrame:
)。其他三种模式允许我们跟踪React Native内容的大小更新。例如,将模式设置为RCTRootViewSizeFlexibilityHeight
将导致React Native测量内容的高度并将该信息传递给RCTRootView
代理人。可以在委托中执行任意操作,包括设置根视图的框架,以便内容符合要求。只有在内容大小发生变化时才会调用委托。
警告:
在JS和本机中使维度灵活导致未定义的行为。例如,flexbox
当您在RCTRootViewSizeFlexibilityWidth
主机上使用时,请勿使顶层React组件的宽度变得灵活(带)RCTRootView
。
我们来看一个例子。
// FlexibleSizeExampleView.m
- (instancetype)initWithFrame:(CGRect)frame
{
[...]
_rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"FlexibilityExampleApp"
initialProperties:@{}];
_rootView.delegate = self;
_rootView.sizeFlexibility = RCTRootViewSizeFlexibilityHeight;
_rootView.frame = CGRectMake(0, 0, self.frame.size.width, 0
}
#pragma mark - RCTRootViewDelegate
- (void)rootViewDidChangeIntrinsicSize:(RCTRootView *)rootView
{
CGRect newFrame = rootView.frame;
newFrame.size = rootView.intrinsicContentSize;
rootView.frame = newFrame;
}
在这个例子中,我们有一个FlexibleSizeExampleView
持有根视图的视图。我们创建根视图,初始化它并设置委托。代表将处理大小更新。然后,我们将根视图的大小灵活性设置为RCTRootViewSizeFlexibilityHeight
,这意味着rootViewDidChangeIntrinsicSize:
每次React Native内容更改高度时都会调用该方法。最后,我们设置根视图的宽度和位置。请注意,我们也设置了高度,但它没有效果,因为我们取决于高度RN。
您可以在这里查看示例的完整源代码。
动态改变根视图的大小灵活模式是很好的。更改根视图的灵活模式将计划布局重新计算,rootViewDidChangeIntrinsicSize:
一旦内容大小已知,将调用委托方法。
注意:
React本机布局计算是在特殊线程上执行的,而本机UI视图更新是在主线程上完成的。这可能会导致本机和React Native之间的临时UI不一致。这是一个已知的问题,我们的团队正在努力同步来自不同来源的UI更新。注意:
React Native不会执行任何布局计算,直到根视图成为其他某些视图的子视图。如果你想隐藏阵营本地视图,直到它的尺寸是已知的,加根视图作为一个子视图,并使其最初是隐藏的(使用UIView
的hidden
属性)。然后在委托方法中更改其可见性。