ReactBridge

Swift 6.0, 5.10, 5.9 Platforms: iOS, macOS, tvOS, visionOS, watchOS Swift Package Manager: compatible React Native: 0.60 Build codecov Swift Doc Coverage

Donate

ReactBridge 提供 Swift 宏,用于 React Native 向 JavaScript 暴露原生模块和 UI 组件。

入门指南

原生模块

@ReactModule 宏附加到您的类定义,它将导出并注册原生模块类到 React Native,这将允许您从 JavaScript 访问其代码。

import React
import ReactBridge

@ReactModule
class CalendarModule: NSObject, RCTBridgeModule {
}

注意 ReactBridge 需要导入 React 库。Swift 类必须继承自 NSObject 并且必须遵循 RCTBridgeModule 协议。

@ReactModule 宏还接受可选的 jsName 参数,用于指定在您的 JavaScript 代码中可以访问的名称。

@ReactModule(jsName: "Calendar")
class CalendarModule: NSObject, RCTBridgeModule {
}

注意 如果您未指定名称,则 JavaScript 模块名称将与 Swift 类名匹配。

现在可以通过以下方式在 JavaScript 中访问原生模块

import { NativeModules } from 'react-native';
const { Calendar } = NativeModules;

方法

除非明确告知,否则 React Native 不会将原生模块中的任何方法暴露给 JavaScript。这可以使用 @ReactMethod 宏来完成。

@ReactModule(jsName: "Calendar")
class CalendarModule: NSObject, RCTBridgeModule {
  
  @ReactMethod
  @objc func createEvent(title: String, location: String) {
    print("Create event '\(title)' at '\(location)'")
  }
}

注意 导出的方法必须使用 @objc 属性标记。

现在您已经拥有了原生模块,您可以调用您的原生方法 createEvent()

Calendar.createEvent('Wedding', 'Las Vegas');

回调

默认情况下,用 @ReactMethod 宏标记的方法是异步的,但如果需要将数据从 Swift 传递到 JavaScript,您可以使用 RCTResponseSenderBlock 类型的回调参数。

@ReactMethod
@objc func createEvent(title: String, location: String, callback: RCTResponseSenderBlock) {
  print("Create event '\(title)' at '\(location)'")
  let eventId = 10;
  callback([eventId])
}

然后可以使用以下方法在 JavaScript 中访问此方法

Calendar.createEvent('Wedding', 'Las Vegas', eventId => {
  console.log(`Created a new event with id ${eventId}`);
});

Promise

原生模块也可以实现 Promise,这可以简化您的 JavaScript,尤其是在使用 async/await 语法时。

@ReactMethod
@objc func createEvent(title: String, location: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
  do {
    let eventId = try createEvent(title: title, location: location)
    resolve(eventId)
  }
  catch let error as NSError {
    reject("\(error.code)", error.localizedDescription, error)
  }
}

此方法的 JavaScript 对应项返回一个 Promise

Calendar.createEvent('Wedding', 'Las Vegas')
  .then(eventId => {
    console.log(`Created a new event with id ${eventId}`);
  })
  .catch(error => {
    console.error(`Error: ${error}`);
  });

继承

可以继承其他原生模块(实现 RCTBridgeModule 协议)并覆盖现有功能或添加附加功能。例如,要向 JavaScript 发送事件,您可以继承 RCTEventEmitter

@ReactModule
class EventEmitter: RCTEventEmitter {
  
  static private(set) var shared: EventEmitter?
  
  override init() {
    super.init()
    Self.shared = self
  }
  
  override func supportedEvents() -> [String]! {
    ["EventReminder"]
  }
  
  func sendReminderEvent(title: String) {
    sendEvent(withName: "EventReminder", body: ["title" : title])
  }
}

...

EventEmitter.shared?.sendReminderEvent(title: "Dinner Party")

然后在 JavaScript 中,您可以使用您的模块创建 NativeEventEmitter 并订阅特定事件

const { EventEmitter } = NativeModules;

this.eventEmitter = new NativeEventEmitter(EventEmitter);
this.emitterSubscription = this.eventEmitter.addListener('EventReminder', event => {
  console.log(event); // Prints: { title: 'Dinner Party' }
});

有关原生模块的更多详细信息,请参见:https://reactnative.net.cn/docs/native-modules-ios

原生 UI 组件

要暴露原生视图,您应该将 @ReactView 宏附加到 RCTViewManager 的子类,该子类通常也是视图的委托,通过桥接将事件发送回 JavaScript。

import React
import ReactBridge
import MapKit

@ReactView
class MapView: RCTViewManager {

  override func view() -> UIView {
    MKMapView()
  }
}

然后您需要一些 JavaScript 来使其成为可用的 React 组件

import {requireNativeComponent} from 'react-native';

const MapView = requireNativeComponent('MapView');

... 
render() {
  return <MapView style={{flex: 1}} />;
}

属性

要桥接原生视图的一些原生属性,我们可以在视图管理器类上声明具有相同名称的属性,并用 @ReactProperty 宏标记它们。假设我们想要能够禁用缩放

@ReactView
class MapView: RCTViewManager {

  @ReactProperty
  var zoomEnabled: Bool?

  override func view() -> UIView {
    MKMapView()
  }
}

注意 视图的目标属性必须对 Objective-C 可见。

现在要真正禁用缩放,我们在 JavaScript 中设置属性

<MapView style={{flex: 1}} zoomEnabled={false} />

对于更复杂的属性,您可以将 json 从 JavaScript 直接传递到视图的本机属性(如果已实现),或者使用 isCustom 参数来通知 React Native 在您的视图管理器上实现了自定义 setter

@ReactView
class MapView: RCTViewManager {

  @ReactProperty
  var zoomEnabled: Bool?
  
  @ReactProperty(isCustom: true)
  var region: [String : Double]?
  
  @objc 
  func set_region(_ json: [String : Double]?, forView: MKMapView?, withDefaultView: MKMapView?) {
    guard let latitude = json?["latitude"],
          let latitudeDelta = json?["latitudeDelta"],
          let longitude = json?["longitude"],
          let longitudeDelta = json?["longitudeDelta"]
    else {
      return
    }
    
    let region = MKCoordinateRegion(
      center: CLLocationCoordinate2D(latitude: latitude, longitude: longitude), 
      span: MKCoordinateSpan(latitudeDelta: latitudeDelta, longitudeDelta: longitudeDelta)
    )
    forView?.setRegion(region, animated: true)
  }
  
  override func view() -> UIView {
    MKMapView()
  }
}

注意 自定义 setter 必须具有以下签名: @objc func set_Name(_ value: Type, forView: ViewType?, withDefaultView: ViewType?)

带有 region 属性的 JavaScript 代码

<MapView
  style={{flex: 1}}
  zoomEnabled={false}
  region={{
    latitude: 37.48,
    longitude: -122.1,
    latitudeDelta: 0.1,
    longitudeDelta: 0.1,
  }}
/>

事件

要处理来自用户的事件(如更改可见区域),我们可以使用 RCTBubblingEventBlock 类型将 JavaScript 的输入事件处理程序映射到原生视图属性。

让我们向 MKMapView 的子类添加新的 onRegionChange 属性

class NativeMapView: MKMapView {
  @objc var onRegionChange: RCTBubblingEventBlock?
}

@ReactView
class MapView: RCTViewManager {
  
  @ReactProperty
  var onRegionChange: RCTBubblingEventBlock?
  
  override func view() -> UIView {
    let mapView = NativeMapView()
    mapView.delegate = self
    return mapView
  }
}

extension MapView: MKMapViewDelegate {
  
  func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    guard let mapView = mapView as? NativeMapView else {
      return
    }
    
    let region = mapView.region
    mapView.onRegionChange?([
      "latitude": region.center.latitude,
      "longitude": region.center.longitude,
      "latitudeDelta": region.span.latitudeDelta,
      "longitudeDelta": region.span.longitudeDelta,
    ])
  }
}

注意 所有带有 RCTBubblingEventBlock 的属性都必须以 on 为前缀,并用 @objc 标记。

调用 onRegionChange 事件处理程序属性会导致在 JavaScript 中调用相同的回调属性

function App(): JSX.Element {
  ...

  this.onRegionChange = event => {
    const region = event.nativeEvent;
    console.log(region.latitude)
  };

  return (
    <MapView
      style={{flex: 1}}
      onRegionChange={this.onRegionChange}
    />
  );
}

有关原生 UI 组件的更多详细信息,请参见:https://reactnative.net.cn/docs/native-components-ios

文档

@ReactModule

该宏导出并注册一个类作为 React Native 的原生模块。

@ReactModule(jsName: String? = nil, requiresMainQueueSetup: Bool = false, methodQueue: DispatchQueue? = nil)

参数

@ReactMethod

该宏将原生模块的方法暴露给 JavaScript。

@ReactMethod(jsName: String? = nil, isSync: Bool = false)

参数

注意 如果您选择同步使用方法,您的应用程序将无法再使用 Google Chrome 调试器。这是因为同步方法需要 JS VM 与应用程序共享内存。对于 Google Chrome 调试器,React Native 在 Google Chrome 中的 JS VM 内运行,并通过 WebSockets 与移动设备异步通信。

@ReactView

该宏导出并注册一个类作为 React Native 的原生 UI 组件。

@ReactView(jsName: String? = nil)

参数

@ReactProperty

该宏将原生视图的属性导出到 JavaScript。

@ReactProperty(keyPath: String? = nil, isCustom: Bool = false)

参数

要求

安装

XCode

  1. 选择 File > Add Package Dependencies...。(注意:菜单选项可能因使用的 Xcode 版本而异。)
  2. 输入包存储库的 URL:https://github.com/ikhvorost/ReactBridge.git
  3. Dependency Rule 输入一个特定的版本或版本范围,并为 Add to Project 输入所需的目标。
  4. 在您的源文件中导入包
import React
import ReactBridge
...

Swift Package

对于 swift 包,您可以直接在您的 Package.swift 文件中将 ReactBridge 添加到您的依赖项中

let package = Package(
    ...
    dependencies: [
        .package(url: "https://github.com/ikhvorost/ReactBridge.git", from: "1.0.0")
    ],
    targets: [
        .target(name: "YourPackage",
            dependencies: [
                .product(name: "ReactBridge", package: "ReactBridge")
            ]
        ),
        ...
    ...
)

许可

本项目根据 MIT 许可证获得许可。有关更多信息,请参见 LICENSE

Donate