和原生端通信
在React Native开发中,与原生端的通信是整合现有应用或使用原生UI组件时的核心需求。本文基于相关文档,系统总结了所有可行的跨语言通信技术,帮助开发者实现React Native与原生组件之间的无缝交互。
简介
React Native继承了React的单向数据流理念,组件间通过props传递信息,依靠回调函数处理状态更新。然而,当混合React Native与原生组件时,需要特殊的跨语言机制来传递信息,以弥补不同环境间的鸿沟。
属性
属性是最简单的跨组件通信方式,支持从原生到React Native以及反向的传递。
从原生组件传递属性到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];
在JS端,组件可以通过this.props访问这些属性:
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>;
}
}
此外,RCTRootView提供了可读写的appProperties属性,设置后React Native应用会重新渲染,但更新必须在主线程中进行。注意,目前存在一个已知问题:在bridge未初始化完成前设置appProperties可能无效。
从React Native传递属性到原生组件
通过RCT_CUSTOM_VIEW_PROPERTY宏在自定义原生组件中导出属性,即可在React Native中像普通组件一样使用这些属性。具体方法可参考相关文档。
属性的限制
跨语言属性的主要缺陷是不支持回调函数,因此无法实现自下而上的数据绑定。例如,当需要从原生父视图中移除一个React Native子视图时,仅靠属性无法传递移除指令。
其他的跨语言交互(事件和原生模块)
当属性无法满足需求时,可以使用更灵活的事件和原生模块机制进行内部或外部通信。
从原生代码调用React Native函数(事件)
事件允许原生代码直接触发JS处理函数,但执行时间不确定,因为处理在单独线程中运行。使用时需注意:事件可能导致依赖混乱、命名空间冲突,且如果需要区分多个React Native组件引用,需在事件中传递标识符(如reactTag)。通常建议集中通过视图管理器的bridge发送事件。
从React Native中调用原生方法(原生模块)
原生模块将Objective-C类暴露给JS,可以导出函数和常量。每个模块实例在通过bridge通信时创建,适用于调用原生库功能(如地理定位)。但需注意,所有原生模块共享命名空间,应避免命名冲突。对于需要更新原生父视图的场景,可通过传递标识符并维护映射来实现。
布局计算流
整合原生与React Native时,布局系统的协同至关重要。
在React Native中嵌入一个原生组件
由于原生视图是UIView的子类,大多数样式和尺寸属性可以直接使用,具体可参考相关文档。
在原生中嵌入一个React Native组件
固定大小的React Native内容
对于已知固定大小的React Native应用,可以直接设置RCTRootView的frame。确保JS内容适应固定尺寸,建议使用flexbox布局,避免绝对定位导致的重叠问题。动态更新frame是可行的,React Native会相应调整布局。
弹性大小的React Native
当内容尺寸动态确定时,有两种解决方案:
- 将React Native视图包裹在
ScrollView中,确保内容可访问且不重叠。 - 使用
RCTRootView的弹性模式,允许JS决定尺寸并传递给宿主视图。弹性模式包括:RCTRootViewSizeFlexibilityNone(默认,固定大小)RCTRootViewSizeFlexibilityWidthRCTRootViewSizeFlexibilityHeightRCTRootViewSizeFlexibilityWidthAndHeight
设置代理后,当内容尺寸变化时,会触发rootViewDidChangeIntrinsicSize:方法,从而调整根视图frame。例如:
_rootView.sizeFlexibility = RCTRootViewSizeFlexibilityHeight;
_rootView.delegate = self;
// 代理方法
- (void)rootViewDidChangeIntrinsicSize:(RCTRootView *)rootView {
CGRect newFrame = rootView.frame;
newFrame.size = rootView.intrinsicContentSize;
rootView.frame = newFrame;
}
注意:避免同时在JS和原生中设置弹性尺寸,以免布局冲突。此外,React Native布局在单独线程计算,而原生UI在主线程更新,可能导致短暂不一致,这是已知问题。在根视图添加为子视图前,布局不会计算,因此如需初始隐藏,可使用hidden属性,然后在代理方法中调整可见性。