原生组件
iOS 原生 UI 组件
本指南以 core React Native library 中已存在的
MapView为例来介绍如何构建一个原生 UI 组件。
接下来对原生的 MKMapView 进行封装,使它可以在 JavaScript 中使用。
原生视图 (native view) 由 RCTViewManager 的子类创建和操作。这些子类在功能上与 UIViewController 相似,但本质上是单例的,每个类只有一个由 bridge 创建的实例。它们把原生视图暴露给 RCTUIManager ,RCTUIManager 委托它们在必要时设置和更新视图的属性。RCTViewManager 通常也是视图的 delegate ,通过 bridge 将事件发送回 JavaScript 。
导出原生 UI 组件的步骤:
子类化
RCTViewManager,为你的组件创建一个 manager;添加
RCT_EXPORT_MODULE()宏;实现
- (UIView *)view方法。
在 Xcode 中添加:
RNTMapManager.h
#import <MapKit/MapKit.h>
#import <React/RCTViewManager.h>
@interface RNTMapManager : RCTViewManager
@endRNTMapManager.m
不要在 - (UIView *)view 导出的 view 实例上设置 frame 或 backgroundColor 等属性,因为 React Native 会覆盖你设置的值,最终生效的是 JavaScript 组件设置的 layout props 。如果你需要更细的控制粒度,最好将你想样式化的 UIView 实例包装在另一个 UIView 中,并返回这个 wrapper UIView 。参考 React Native issue 2948 。
在 VS Code 中添加:
MapView.js
注意:不可在
MapView.js中执行⌘S,否则会报错:“There are two approaches to error handling with callbacks” 。原因是requireNativeComponent被执行了两次。如果报错了,可在其他文件(比如App.js)执行⌘S或在 Metro 终端任务中输入r来刷新项目。
App.js
这现在是 JavaScript 中的一个功能齐全的原生地图视图组件,包含缩放和其他本地手势支持。但我们还不能从 JavaScript 控制它 :(
导出属性
要使该组件更可用,我们可以做的第一件事是在一些原生属性上进行桥接。比如我们想禁用缩放手势和指定可视区域。
示例一:导出 zoomEnabled 属性
缩放手势通过 zoomEnabled 属性控制,这是一个布尔值,在 RNTMapManager.m 中添加:
其中 RCT_EXPORT_VIEW_PROPERTY(name, type) 宏的本质是一个方法:
在 JS 中使用 MapView 时,就能设置 zoomEnabled 属性了。在 JS 中禁用缩放:
为了记录 MapView 组件的属性(以及它们接受的值),我们将添加一个 wrapper 组件,并使用 React PropTypes 记录接口:
示例二:导出 region 属性
接下来,我们再添加一个更复杂的 region prop ,在 RNTMapManager.m 中添加:
在上述代码中,json 是从 JS 传入的原始值。还有一个 view 变量,它允许我们访问 manager 的 view 实例,最后是 defaultView 变量,如果 JS 给我们发送一个空值,我们使用它将属性重置为默认值。
其中 RCT_CUSTOM_VIEW_PROPERTY(name, type, viewClass) 宏的本质是包装了一个方法头,提供了 json、view、defaultView 三个属性给后面的方法体使用:
在 RNTMapManager.m 中添加一个 RCTConvert (Mapkit) 分类,提供一个 + MKCoordinateRegion: 方法来把 JS 传入的 json 转成 MKCoordinateRegion ,在这个分类中使用了 React Native 库中已有的 RCTConvert+CoreLocation 分类中的方法:
在 MapView.js 的 propTypes 中添加 region :
在 App.js 中为 MapView 设置 region 属性:
导出事件的回调
现在我们有了一个原生的 map 组件,我们可以从 JS 中自由控制它,但是我们如何处理来自用户的事件,比如当用户缩放或平移 map 来改变可见区域时,如何将事件传递给 JS ?
在之前的代码中,我们在 manager 的 - (UIView *)view 方法中返回的是 MKMapView 的实例,由于我们无法往 MKMapView 中添加新属性,因此需要新建一个 MKMapView 的子类 RNTMapView ,然后在这个类中添加名为 onRegionChange 的 RCTBubblingEventBlock 类型的属性。
要注意的是,所有的
RCTBubblingEventBlock类型的属性,必须以on开头。
对 RNTMapManager 的修改:
在
RNTMapManager中使用RCT_EXPORT_VIEW_PROPERTY宏添加onRegionChange属性;在
- (UIView *)view中返回RNTMapView的实例;RNTMapManager类遵守MKMapViewDelegate协议,在- mapView:regionDidChangeAnimated:中调用onRegionChange回调即可调用 JavaScript 中对应的回调 prop ;
在 MapView.js 的类和 propTypes 中添加 onRegionChange 相关逻辑:
在 App.js 中添加 onRegionChange :
Handling multiple native views
https://reactnative.dev/docs/native-components-ios#handling-multiple-native-views
Styles
https://reactnative.dev/docs/native-components-ios#styles
Direct Manipulation
https://reactnative.dev/docs/direct-manipulation#composite-components-and-setnativeprops
Last updated