React Native与原生端通信技术全面解析

Viewed 0

和原生端通信

在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

当内容尺寸动态确定时,有两种解决方案:

  1. 将React Native视图包裹在ScrollView中,确保内容可访问且不重叠。
  2. 使用RCTRootView的弹性模式,允许JS决定尺寸并传递给宿主视图。弹性模式包括:
    • RCTRootViewSizeFlexibilityNone(默认,固定大小)
    • RCTRootViewSizeFlexibilityWidth
    • RCTRootViewSizeFlexibilityHeight
    • RCTRootViewSizeFlexibilityWidthAndHeight

设置代理后,当内容尺寸变化时,会触发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属性,然后在代理方法中调整可见性。

0 Answers