iOS 原生模块
Native Module 和 Native Components 是我们旧版架构使用的稳定技术。当新架构稳定后,它们将被弃用。新架构使用 Turbo Native 模块 和 Fabric 原生组件 来实现类似的结果。
¥Native Module and Native Components are our stable technologies used by the legacy architecture. They will be deprecated in the future when the New Architecture will be stable. The New Architecture uses Turbo Native Module and Fabric Native Components to achieve similar results.
欢迎使用 iOS 原生模块。请首先阅读 原生模块介绍 以了解原生模块是什么。
¥Welcome to Native Modules for iOS. Please start by reading the Native Modules Intro for an intro to what native modules are.
创建日历原生模块
¥Create a Calendar Native Module
在以下指南中,你将创建一个原生模块 CalendarModule
,它允许你从 JavaScript 访问 Apple 的日历 API。最后,你将能够从 JavaScript 调用 CalendarModule.createCalendarEvent('Dinner Party', 'My House');
,调用创建日历事件的原生方法。
¥In the following guide you will create a native module, CalendarModule
, that will allow you to access Apple's calendar APIs from JavaScript. By the end you will be able to call CalendarModule.createCalendarEvent('Dinner Party', 'My House');
from JavaScript, invoking a native method that creates a calendar event.
设置
¥Setup
首先,在 Xcode 中打开 React Native 应用中的 iOS 项目。你可以在 React Native 应用中找到你的 iOS 项目:
¥To get started, open up the iOS project within your React Native application in Xcode. You can find your iOS project here within a React Native app:
我们建议使用 Xcode 编写原生代码。Xcode 是为 iOS 开发而构建的,使用它可以帮助你快速解决代码语法等较小的错误。
¥We recommend using Xcode to write your native code. Xcode is built for iOS development, and using it will help you to quickly resolve smaller errors like code syntax.
创建自定义原生模块文件
¥Create Custom Native Module Files
第一步是创建我们的主要自定义原生模块标头和实现文件。创建一个名为 RCTCalendarModule.h
的新文件
¥The first step is to create our main custom native module header and implementation files. Create a new file called RCTCalendarModule.h
并向其中添加以下内容:
¥and add the following to it:
// RCTCalendarModule.h
#import <React/RCTBridgeModule.h>
@interface RCTCalendarModule : NSObject <RCTBridgeModule>
@end
你可以使用任何适合你正在构建的原生模块的名称。由于你正在创建日历原生模块,因此将类命名为 RCTCalendarModule
。由于 ObjC 没有对 Java 或 C++ 等命名空间的语言级支持,因此约定是在类名前面添加子字符串。这可能是你的应用名称或基础设施名称的缩写。在本例中,RCT 指的是 React。
¥You can use any name that fits the native module you are building. Name the class RCTCalendarModule
since you are creating a calendar native module. Since ObjC does not have language-level support for namespaces like Java or C++, convention is to prepend the class name with a substring. This could be an abbreviation of your application name or your infra name. RCT, in this example, refers to React.
如下所示,CalendarModule 类实现了 RCTBridgeModule
协议。原生模块是实现 RCTBridgeModule
协议的 Objective-C 类。
¥As you can see below, the CalendarModule class implements the RCTBridgeModule
protocol. A native module is an Objective-C class that implements the RCTBridgeModule
protocol.
接下来,让我们开始实现原生模块。在 xcode、RCTCalendarModule.m
中使用 cocoa touch 类在同一文件夹中创建相应的实现文件,并包含以下内容:
¥Next up, let’s start implementing the native module. Create the corresponding implementation file using cocoa touch class in xcode, RCTCalendarModule.m
, in the same folder and include the following content:
// RCTCalendarModule.m
#import "RCTCalendarModule.h"
@implementation RCTCalendarModule
// To export a module named RCTCalendarModule
RCT_EXPORT_MODULE();
@end
模块名称
¥Module Name
目前,你的 RCTCalendarModule.m
原生模块仅包含 RCT_EXPORT_MODULE
宏,该宏使用 React Native 导出并注册原生模块类。RCT_EXPORT_MODULE
宏还采用一个可选参数,该参数指定可在 JavaScript 代码中访问的模块的名称。
¥For now, your RCTCalendarModule.m
native module only includes a RCT_EXPORT_MODULE
macro, which exports and registers the native module class with React Native. The RCT_EXPORT_MODULE
macro also takes an optional argument that specifies the name that the module will be accessible as in your JavaScript code.
该参数不是字符串字面量。在下面的示例中,传递的是 RCT_EXPORT_MODULE(CalendarModuleFoo)
,而不是 RCT_EXPORT_MODULE("CalendarModuleFoo")
。
¥This argument is not a string literal. In the example below RCT_EXPORT_MODULE(CalendarModuleFoo)
is passed, not RCT_EXPORT_MODULE("CalendarModuleFoo")
.
// To export a module named CalendarModuleFoo
RCT_EXPORT_MODULE(CalendarModuleFoo);
然后可以在 JS 中访问原生模块,如下所示:
¥The native module can then be accessed in JS like this:
const {CalendarModuleFoo} = ReactNative.NativeModules;
如果不指定名称,JavaScript 模块名称将与 Objective-C 类名称匹配,并删除任何 "RCT" 或 "RK" 前缀。
¥If you do not specify a name, the JavaScript module name will match the Objective-C class name, with any "RCT" or "RK" prefixes removed.
让我们按照下面的示例,在不带任何参数的情况下调用 RCT_EXPORT_MODULE
。因此,该模块将使用名称 CalendarModule
暴露给 React Native,因为这是 Objective-C 类名,并且删除了 RCT。
¥Let's follow the example below and call RCT_EXPORT_MODULE
without any arguments. As a result, the module will be exposed to React Native using the name CalendarModule
, since that is the Objective-C class name, with RCT removed.
// Without passing in a name this will export the native module name as the Objective-C class name with “RCT” removed
RCT_EXPORT_MODULE();
然后可以在 JS 中访问原生模块,如下所示:
¥The native module can then be accessed in JS like this:
const {CalendarModule} = ReactNative.NativeModules;
将原生方法导出到 JavaScript
¥Export a Native Method to JavaScript
React Native 不会将原生模块中的任何方法公开给 JavaScript,除非明确告知。这可以使用 RCT_EXPORT_METHOD
宏来完成。RCT_EXPORT_METHOD
宏中编写的方法是异步的,因此返回类型始终为 void。为了将 RCT_EXPORT_METHOD
方法的结果传递给 JavaScript,你可以使用回调或触发事件(如下所述)。让我们继续使用 RCT_EXPORT_METHOD
宏为 CalendarModule
原生模块设置原生方法。将其命名为 createCalendarEvent()
,现在让它以字符串形式接收名称和位置参数。参数类型选项将很快介绍。
¥React Native will not expose any methods in a native module to JavaScript unless explicitly told to. This can be done using the RCT_EXPORT_METHOD
macro. Methods written in the RCT_EXPORT_METHOD
macro are asynchronous and the return type is therefore always void. In order to pass a result from a RCT_EXPORT_METHOD
method to JavaScript you can use callbacks or emit events (covered below). Let’s go ahead and set up a native method for our CalendarModule
native module using the RCT_EXPORT_METHOD
macro. Call it createCalendarEvent()
and for now have it take in name and location arguments as strings. Argument type options will be covered shortly.
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name location:(NSString *)location)
{
}
请注意,TurboModule 不需要
RCT_EXPORT_METHOD
宏,除非你的方法依赖于 RCT 参数转换(请参阅下面的参数类型)。最终,React Native 将删除RCT_EXPORT_MACRO,
,因此我们不鼓励人们使用RCTConvert
。相反,你可以在方法主体内进行参数转换。¥Please note that the
RCT_EXPORT_METHOD
macro will not be necessary with TurboModules unless your method relies on RCT argument conversion (see argument types below). Ultimately, React Native will removeRCT_EXPORT_MACRO,
so we discourage people from usingRCTConvert
. Instead, you can do the argument conversion within the method body.
在构建 createCalendarEvent()
方法的功能之前,请在该方法中添加控制台日志,以便你可以确认它已从 React Native 应用中的 JavaScript 调用。使用 React 中的 RCTLog
API。让我们在文件顶部导入该标头,然后添加日志调用。
¥Before you build out the createCalendarEvent()
method’s functionality, add a console log in the method so you can confirm it has been invoked from JavaScript in your React Native application. Use the RCTLog
APIs from React. Let’s import that header at the top of your file and then add the log call.
#import <React/RCTLog.h>
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name location:(NSString *)location)
{
RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
}
同步方法
¥Synchronous Methods
你可以使用 RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD
创建同步原生方法。
¥You can use the RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD
to create a synchronous native method.
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getName)
{
return [[UIDevice currentDevice] name];
}
该方法的返回类型必须是对象类型(id)并且应该可序列化为 JSON。这意味着该钩子只能返回 nil 或 JSON 值(例如 NSNumber、NSString、NSArray、NSDictionary)。
¥The return type of this method must be of object type (id) and should be serializable to JSON. This means that the hook can only return nil or JSON values (e.g. NSNumber, NSString, NSArray, NSDictionary).
目前,我们不建议使用同步方法,因为同步调用方法可能会产生严重的性能损失,并向原生模块引入与线程相关的错误。此外,请注意,如果你选择使用 RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD
,你的应用将无法再使用 Google Chrome 调试器。这是因为同步方法需要 JS VM 与应用共享内存。对于 Google Chrome 调试器,React Native 在 Google Chrome 中的 JS VM 内部运行,并通过 WebSocket 与移动设备进行异步通信。
¥At the moment, we do not recommend using synchronous methods, since calling methods synchronously can have strong performance penalties and introduce threading-related bugs to your native modules. Additionally, please note that if you choose to use RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD
, your app can no longer use the Google Chrome debugger. This is because synchronous methods require the JS VM to share memory with the app. For the Google Chrome debugger, React Native runs inside the JS VM in Google Chrome, and communicates asynchronously with the mobile devices via WebSockets.
测试你所构建的内容
¥Test What You Have Built
至此,你已经为 iOS 中的原生模块设置了基本的脚手架。通过访问原生模块并在 JavaScript 中调用其导出方法来测试这一点。
¥At this point you have set up the basic scaffolding for your native module in iOS. Test that out by accessing the native module and invoking it’s exported method in JavaScript.
在应用中找到一个要添加对原生模块的 createCalendarEvent()
方法的调用的位置。以下是你可以在应用中添加的组件 NewModuleButton
的示例。你可以在 NewModuleButton
的 onPress()
函数中调用原生模块。
¥Find a place in your application where you would like to add a call to the native module’s createCalendarEvent()
method. Below is an example of a component, NewModuleButton
you can add in your app. You can invoke the native module inside NewModuleButton
's onPress()
function.
import React from 'react';
import {NativeModules, Button} from 'react-native';
const NewModuleButton = () => {
const onPress = () => {
console.log('We will invoke the native module here!');
};
return (
<Button
title="Click to invoke your native module!"
color="#841584"
onPress={onPress}
/>
);
};
export default NewModuleButton;
为了从 JavaScript 访问你的原生模块,你需要首先从 React Native 导入 NativeModules
:
¥In order to access your native module from JavaScript you need to first import NativeModules
from React Native:
import {NativeModules} from 'react-native';
然后,你可以从 NativeModules
访问 CalendarModule
原生模块。
¥You can then access the CalendarModule
native module off of NativeModules
.
const {CalendarModule} = NativeModules;
现在你已经有了可用的 CalendarModule 原生模块,你可以调用原生方法 createCalendarEvent()
。下面在 NewModuleButton
中添加到 onPress()
方法中:
¥Now that you have the CalendarModule native module available, you can invoke your native method createCalendarEvent()
. Below it is added to the onPress()
method in NewModuleButton
:
const onPress = () => {
CalendarModule.createCalendarEvent('testName', 'testLocation');
};
最后一步是重建 React Native 应用,以便你可以获得最新的原生代码(以及新的原生模块!)。在 React Native 应用所在的命令行中,运行以下命令:
¥The final step is to rebuild the React Native app so that you can have the latest native code (with your new native module!) available. In your command line, where the react native application is located, run the following :
- npm
- Yarn
npm run ios
yarn ios
边迭代边构建
¥Building as You Iterate
当你学习这些指南并迭代原生模块时,你将需要对应用进行原生重建,以访问 JavaScript 的最新更改。这是因为你正在编写的代码位于应用的原生部分中。虽然 React Native 的 Metro 打包器可以监视 JavaScript 中的更改并为你即时重建 JS 打包包,但它不会对原生代码执行此操作。因此,如果你想测试最新的原生更改,你需要使用上述命令进行重建。
¥As you work through these guides and iterate on your native module, you will need to do a native rebuild of your application to access your most recent changes from JavaScript. This is because the code that you are writing sits within the native part of your application. While React Native’s metro bundler can watch for changes in JavaScript and rebuild JS bundle on the fly for you, it will not do so for native code. So if you want to test your latest native changes you need to rebuild by using the above command.
回顾 ✨
¥Recap✨
你现在应该能够在 JavaScript 中调用原生模块上的 createCalendarEvent()
方法。由于你在函数中使用 RCTLog
,因此你可以确认你的原生方法正在被 在你的应用中启用调试模式 调用,并查看 Chrome 中的 JS 控制台或移动应用调试器 Flipper。每次调用原生模块方法时,你都应该看到 RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
消息。
¥You should now be able to invoke your createCalendarEvent()
method on your native module in JavaScript. Since you are using RCTLog
in the function, you can confirm your native method is being invoked by enabling debug mode in your app and looking at the JS console in Chrome or the mobile app debugger Flipper. You should see your RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
message each time you invoke the native module method.
此时,你已经创建了一个 iOS 原生模块,并在 React Native 应用中从 JavaScript 调用了它的方法。你可以继续阅读以了解更多有关原生模块方法采用的参数类型以及如何在原生模块中设置回调和 promise 等信息。
¥At this point you have created an iOS native module and invoked a method on it from JavaScript in your React Native application. You can read on to learn more about things like what argument types your native module method takes and how to setup callbacks and promises within your native module.
超越日历原生模块
¥Beyond a Calendar Native Module
更好的原生模块导出
¥Better Native Module Export
像上面那样通过从 NativeModules
中拉出原生模块来导入它有点笨拙。
¥Importing your native module by pulling it off of NativeModules
like above is a bit clunky.
为了避免原生模块的使用者每次想要访问原生模块时都需要执行此操作,你可以为该模块创建一个 JavaScript 封装器。创建一个名为 NativeCalendarModule.js 的新 JavaScript 文件,其中包含以下内容:
¥To save consumers of your native module from needing to do that each time they want to access your native module, you can create a JavaScript wrapper for the module. Create a new JavaScript file named NativeCalendarModule.js with the following content:
/**
* This exposes the native CalendarModule module as a JS module. This has a
* function 'createCalendarEvent' which takes the following parameters:
* 1. String name: A string representing the name of the event
* 2. String location: A string representing the location of the event
*/
import {NativeModules} from 'react-native';
const {CalendarModule} = NativeModules;
export default CalendarModule;
此 JavaScript 文件也成为你添加任何 JavaScript 端功能的好位置。例如,如果你使用像 TypeScript 这样的类型系统,你可以在此处为原生模块添加类型注释。虽然 React Native 还不支持 Native 到 JS 的类型安全,但有了这些类型注释,你的所有 JS 代码都将是类型安全的。这些注释还将使你更轻松地切换到类型安全的原生模块。下面是向日历模块添加类型安全的示例:
¥This JavaScript file also becomes a good location for you to add any JavaScript side functionality. For example, if you use a type system like TypeScript you can add type annotations for your native module here. While React Native does not yet support Native to JS type safety, with these type annotations, all your JS code will be type safe. These annotations will also make it easier for you to switch to type-safe native modules down the line. Below is an example of adding type safety to the Calendar Module:
/**
* This exposes the native CalendarModule module as a JS module. This has a
* function 'createCalendarEvent' which takes the following parameters:
* * 1. String name: A string representing the name of the event
* 2. String location: A string representing the location of the event
*/
import {NativeModules} from 'react-native';
const {CalendarModule} = NativeModules;
interface CalendarInterface {
createCalendarEvent(name: string, location: string): void;
}
export default CalendarModule as CalendarInterface;
在其他 JavaScript 文件中,你可以访问原生模块并调用其方法,如下所示:
¥In your other JavaScript files you can access the native module and invoke its method like this:
import NativeCalendarModule from './NativeCalendarModule';
NativeCalendarModule.createCalendarEvent('foo', 'bar');
请注意,这假设你导入
CalendarModule
的位置与NativeCalendarModule.js
位于同一层次结构中。请根据需要更新相对导入。¥Note this assumes that the place you are importing
CalendarModule
is in the same hierarchy asNativeCalendarModule.js
. Please update the relative import as necessary.
参数类型
¥Argument Types
当在 JavaScript 中调用原生模块方法时,React Native 会将参数从 JS 对象转换为其 Objective-C/Swift 对象类似物。例如,如果你的 Objective-C Native Module 方法接受 NSNumber,那么在 JS 中你需要使用数字来调用该方法。React Native 将为你处理转换。下面是原生模块方法支持的参数类型及其映射到的 JavaScript 等效项的列表。
¥When a native module method is invoked in JavaScript, React Native converts the arguments from JS objects to their Objective-C/Swift object analogues. So for example, if your Objective-C Native Module method accepts a NSNumber, in JS you need to call the method with a number. React Native will handle the conversion for you. Below is a list of the argument types supported for native module methods and the JavaScript equivalents they map to.
Objective-C | JavaScript |
---|---|
NS 字符串 | 字符串,?字符串 |
BOOL | boolean |
double | number |
NS 编号 | ?数字 |
NSArray | 数组,?数组 |
NS 词典 | 对象,?对象 |
RCT 响应发送方块 | 功能(成功) |
RCTResponseSenderBlock、RCTResponseErrorBlock | 功能(失败) |
RCTPromiseResolveBlock、RCTPromiseRejectBlock | Promise |
目前支持以下类型,但 TurboModule 中将不支持。请避免使用它们。
¥The following types are currently supported but will not be supported in TurboModules. Please avoid using them.
功能(失败)-> RCTResponseErrorBlock
¥Function (failure) -> RCTResponseErrorBlock
数字 -> NSInteger
¥Number -> NSInteger
数字 -> CGFloat
¥Number -> CGFloat
数字 -> 浮动
¥Number -> float
对于 iOS,你还可以使用 RCTConvert
类支持的任何参数类型编写原生模块方法(有关支持的详细信息,请参阅 RCTConvert)。RCTConvert 辅助函数都接受 JSON 值作为输入,并将其映射到原生 Objective-C 类型或类。
¥For iOS, you can also write native module methods with any argument type that is supported by the RCTConvert
class (see RCTConvert for details about what is supported). The RCTConvert helper functions all accept a JSON value as input and map it to a native Objective-C type or class.
导出常量
¥Exporting Constants
原生模块可以通过重写原生方法 constantsToExport()
来导出常量。下面的 constantsToExport()
被覆盖,并返回一个包含默认事件名称属性的字典,你可以像这样在 JavaScript 中访问:
¥A native module can export constants by overriding the native method constantsToExport()
. Below constantsToExport()
is overriden, and returns a Dictionary that contains a default event name property you can access in JavaScript like so:
- (NSDictionary *)constantsToExport
{
return @{ @"DEFAULT_EVENT_NAME": @"New Event" };
}
然后可以通过在 JS 中调用原生模块上的 getConstants()
来访问该常量,如下所示:
¥The constant can then be accessed by invoking getConstants()
on the native module in JS like so:
const {DEFAULT_EVENT_NAME} = CalendarModule.getConstants();
console.log(DEFAULT_EVENT_NAME);
从技术上讲,可以直接从 NativeModule
对象访问 constantsToExport()
中导出的常量。TurboModules 将不再支持此功能,因此我们鼓励社区改用上述方法,以避免必要的迁移。
¥Technically, it is possible to access constants exported in constantsToExport()
directly off the NativeModule
object. This will no longer be supported with TurboModules, so we encourage the community to switch to the above approach to avoid necessary migration down the line.
请注意,常量仅在初始化时导出,因此如果你在运行时更改
constantsToExport()
值,则不会影响 JavaScript 环境。¥Note that the constants are exported only at initialization time, so if you change
constantsToExport()
values at runtime it won't affect the JavaScript environment.
对于 iOS,如果你覆盖 constantsToExport()
,那么你还应该实现 + requiresMainQueueSetup
,以便让 React Native 知道你的模块是否需要在执行任何 JavaScript 代码之前在主线程上初始化。否则,你将看到一条警告,表明将来你的模块可能会在后台线程上初始化,除非你明确使用 + requiresMainQueueSetup:
选择退出。如果你的模块不需要访问 UIKit,那么你应该用 NO 响应 + requiresMainQueueSetup
。
¥For iOS, if you override constantsToExport()
then you should also implement + requiresMainQueueSetup
to let React Native know if your module needs to be initialized on the main thread, before any JavaScript code executes. Otherwise you will see a warning that in the future your module may be initialized on a background thread unless you explicitly opt out with + requiresMainQueueSetup:
. If your module does not require access to UIKit, then you should respond to + requiresMainQueueSetup
with NO.
回调
¥Callbacks
原生模块还支持一种独特的参数 - 回调。回调用于将数据从 Objective-C 传递到 JavaScript 以实现异步方法。它们还可以用于从原生端异步执行 JS。
¥Native modules also support a unique kind of argument - a callback. Callbacks are used to pass data from Objective-C to JavaScript for asynchronous methods. They can also be used to asynchronously execute JS from the native side.
对于 iOS,回调是使用类型 RCTResponseSenderBlock
实现的。下面将回调参数 myCallBack
添加到 createCalendarEventMethod()
中:
¥For iOS, callbacks are implemented using the type RCTResponseSenderBlock
. Below the callback parameter myCallBack
is added to the createCalendarEventMethod()
:
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)title
location:(NSString *)location
myCallback:(RCTResponseSenderBlock)callback)
然后,你可以在原生函数中调用回调,以数组形式提供你想要传递给 JavaScript 的任何结果。请注意,RCTResponseSenderBlock
仅接受一个参数 - 要传递给 JavaScript 回调的参数数组。下面你将传回先前调用中创建的事件的 ID。
¥You can then invoke the callback in your native function, providing whatever result you want to pass to JavaScript in an array. Note that RCTResponseSenderBlock
accepts only one argument - an array of parameters to pass to the JavaScript callback. Below you will pass back the ID of an event created in an earlier call.
需要强调的是,回调不会在原生函数完成后立即调用,请记住通信是异步的。
¥It is important to highlight that the callback is not invoked immediately after the native function completes—remember the communication is asynchronous.
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)title location:(NSString *)location callback: (RCTResponseSenderBlock)callback)
{
NSInteger eventId = ...
callback(@[@(eventId)]);
RCTLogInfo(@"Pretending to create an event %@ at %@", title, location);
}
然后可以使用以下命令在 JavaScript 中访问该方法:
¥This method could then be accessed in JavaScript using the following:
const onSubmit = () => {
CalendarModule.createCalendarEvent(
'Party',
'04-12-2020',
eventId => {
console.log(`Created a new event with id ${eventId}`);
},
);
};
原生模块应该只调用其回调一次。但是,它可以存储回调并稍后调用它。此模式通常用于封装需要委托的 iOS API - 请参阅 RCTAlertManager
的示例。如果从未调用回调,则会泄漏一些内存。
¥A native module is supposed to invoke its callback only once. It can, however, store the callback and invoke it later. This pattern is often used to wrap iOS APIs that require delegates— see RCTAlertManager
for an example. If the callback is never invoked, some memory is leaked.
有两种使用回调处理错误的方法。第一个是遵循 Node 的约定,并将传递给回调数组的第一个参数视为错误对象。
¥There are two approaches to error handling with callbacks. The first is to follow Node’s convention and treat the first argument passed to the callback array as an error object.
RCT_EXPORT_METHOD(createCalendarEventCallback:(NSString *)title location:(NSString *)location callback: (RCTResponseSenderBlock)callback)
{
NSNumber *eventId = [NSNumber numberWithInt:123];
callback(@[[NSNull null], eventId]);
}
在 JavaScript 中,你可以检查第一个参数以查看是否传递了错误:
¥In JavaScript, you can then check the first argument to see if an error was passed through:
const onPress = () => {
CalendarModule.createCalendarEventCallback(
'testName',
'testLocation',
(error, eventId) => {
if (error) {
console.error(`Error found! ${error}`);
}
console.log(`event id ${eventId} returned`);
},
);
};
另一种选择是使用两个单独的回调:onFailure 和 onSuccess。
¥Another option is to use two separate callbacks: onFailure and onSuccess.
RCT_EXPORT_METHOD(createCalendarEventCallback:(NSString *)title
location:(NSString *)location
errorCallback: (RCTResponseSenderBlock)errorCallback
successCallback: (RCTResponseSenderBlock)successCallback)
{
@try {
NSNumber *eventId = [NSNumber numberWithInt:123];
successCallback(@[eventId]);
}
@catch ( NSException *e ) {
errorCallback(@[e]);
}
}
然后在 JavaScript 中,你可以为错误和成功响应添加单独的回调:
¥Then in JavaScript you can add a separate callback for error and success responses:
const onPress = () => {
CalendarModule.createCalendarEventCallback(
'testName',
'testLocation',
error => {
console.error(`Error found! ${error}`);
},
eventId => {
console.log(`event id ${eventId} returned`);
},
);
};
如果你想将类似错误的对象传递给 JavaScript,请使用 RCTMakeError
和 RCTUtils.h.
目前这只是将一个 Error 形状的字典传递给 JavaScript,但 React Native 的目标是在未来自动生成真正的 JavaScript Error 对象。你还可以提供 RCTResponseErrorBlock
参数,该参数用于错误回调并接受 NSError \* object
。请注意,TurboModule 不支持此参数类型。
¥If you want to pass error-like objects to JavaScript, use RCTMakeError
from RCTUtils.h.
Right now this only passes an Error-shaped dictionary to JavaScript, but React Native aims to automatically generate real JavaScript Error objects in the future. You can also provide a RCTResponseErrorBlock
argument, which is used for error callbacks and accepts an NSError \* object
. Please note that this argument type will not be supported with TurboModules.
Promise
原生模块还可以实现一个 promise,它可以简化你的 JavaScript,特别是在使用 ES2016 的 async/await
语法时。当原生模块方法的最后一个参数为 RCTPromiseResolveBlock
和 RCTPromiseRejectBlock
时,其对应的 JS 方法将返回一个 JS Promise 对象。
¥Native modules can also fulfill a promise, which can simplify your JavaScript, especially when using ES2016's async/await
syntax. When the last parameter of a native module method is a RCTPromiseResolveBlock
and RCTPromiseRejectBlock
, its corresponding JS method will return a JS Promise object.
重构上面的代码以使用 Promise 而不是回调,如下所示:
¥Refactoring the above code to use a promise instead of callbacks looks like this:
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)title
location:(NSString *)location
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSInteger eventId = createCalendarEvent();
if (eventId) {
resolve(@(eventId));
} else {
reject(@"event_failure", @"no event id returned", nil);
}
}
此方法的 JavaScript 对应项返回一个 Promise。这意味着你可以在异步函数中使用 await
关键字来调用它并等待其结果:
¥The JavaScript counterpart of this method returns a Promise. This means you can use the await
keyword within an async function to call it and wait for its result:
const onSubmit = async () => {
try {
const eventId = await CalendarModule.createCalendarEvent(
'Party',
'my house',
);
console.log(`Created a new event with id ${eventId}`);
} catch (e) {
console.error(e);
}
};
将事件发送到 JavaScript
¥Sending Events to JavaScript
原生模块可以向 JavaScript 触发事件信号,而无需直接调用。例如,你可能希望向 JavaScript 发出提醒信号,提醒你原生 iOS 日历应用中的日历事件即将发生。首选方法是子类 RCTEventEmitter
,实现 supportedEvents
并调用 self sendEventWithName
:
¥Native modules can signal events to JavaScript without being invoked directly. For example, you might want to signal to JavaScript a reminder that a calendar event from the native iOS calendar app will occur soon. The preferred way to do this is to subclass RCTEventEmitter
, implement supportedEvents
and call self sendEventWithName
:
更新你的标头类以导入 RCTEventEmitter
和子类 RCTEventEmitter
:
¥Update your header class to import RCTEventEmitter
and subclass RCTEventEmitter
:
// CalendarModule.h
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface CalendarModule : RCTEventEmitter <RCTBridgeModule>
@end
JavaScript 代码可以通过在模块周围创建新的 NativeEventEmitter
实例来订阅这些事件。
¥JavaScript code can subscribe to these events by creating a new NativeEventEmitter
instance around your module.
如果在没有监听器的情况下触发事件而不必要地消耗资源,你将收到警告。为了避免这种情况,并优化模块的工作负载(例如,通过取消订阅上游通知或暂停后台任务),你可以覆盖 RCTEventEmitter
子类中的 startObserving
和 stopObserving
。
¥You will receive a warning if you expend resources unnecessarily by emitting an event while there are no listeners. To avoid this, and to optimize your module's workload (e.g. by unsubscribing from upstream notifications or pausing background tasks), you can override startObserving
and stopObserving
in your RCTEventEmitter
subclass.
@implementation CalendarModule
{
bool hasListeners;
}
// Will be called when this module's first listener is added.
-(void)startObserving {
hasListeners = YES;
// Set up any upstream listeners or background tasks as necessary
}
// Will be called when this module's last listener is removed, or on dealloc.
-(void)stopObserving {
hasListeners = NO;
// Remove upstream listeners, stop unnecessary background tasks
}
- (void)calendarEventReminderReceived:(NSNotification *)notification
{
NSString *eventName = notification.userInfo[@"name"];
if (hasListeners) {// Only send events if anyone is listening
[self sendEventWithName:@"EventReminder" body:@{@"name": eventName}];
}
}
螺纹加工
¥Threading
除非原生模块提供自己的方法队列,否则它不应该对调用它的线程做出任何假设。目前,如果原生模块不提供方法队列,React Native 会为其创建一个单独的 GCD 队列并在那里调用其方法。请注意,这是一个实现细节,可能会发生变化。如果要显式地为原生模块提供方法队列,请覆盖原生模块中的 (dispatch_queue_t) methodQueue
方法。例如,如果需要使用仅限主线程的 iOS API,则应通过以下方式指定:
¥Unless the native module provides its own method queue, it shouldn't make any assumptions about what thread it's being called on. Currently, if a native module doesn't provide a method queue, React Native will create a separate GCD queue for it and invoke its methods there. Please note that this is an implementation detail and might change. If you want to explicitly provide a method queue for a native module, override the (dispatch_queue_t) methodQueue
method in the native module. For example, if it needs to use a main-thread-only iOS API, it should specify this via:
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
类似地,如果某个操作可能需要很长时间才能完成,则原生模块可以指定自己的队列来运行操作。同样,目前 React Native 将为你的原生模块提供一个单独的方法队列,但这是你不应该依赖的实现细节。如果你不提供自己的方法队列,将来你的原生模块的长时间运行操作可能最终会阻止在其他不相关的原生模块上执行的异步调用。例如,这里的 RCTAsyncLocalStorage
模块创建了自己的队列,因此 React 队列不会因等待可能较慢的磁盘访问而被阻塞。
¥Similarly, if an operation may take a long time to complete, the native module can specify its own queue to run operations on. Again, currently React Native will provide a separate method queue for your native module, but this is an implementation detail you should not rely on. If you don't provide your own method queue, in the future, your native module's long running operations may end up blocking async calls being executed on other unrelated native modules. The RCTAsyncLocalStorage
module here, for example, creates its own queue so the React queue isn't blocked waiting on potentially slow disk access.
- (dispatch_queue_t)methodQueue
{
return dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL);
}
指定的 methodQueue
将由模块中的所有方法共享。如果只有一个方法需要长时间运行(或者由于某种原因需要在与其他方法不同的队列上运行),则可以在方法内部使用 dispatch_async
在另一个队列上执行该特定方法的代码,而不会影响其他方法:
¥The specified methodQueue
will be shared by all of the methods in your module. If only one of your methods is long-running (or needs to be run on a different queue than the others for some reason), you can use dispatch_async
inside the method to perform that particular method's code on another queue, without affecting the others:
RCT_EXPORT_METHOD(doSomethingExpensive:(NSString *)param callback:(RCTResponseSenderBlock)callback)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Call long-running code on background thread
...
// You can invoke callback from any thread/queue
callback(@[...]);
});
}
在模块之间共享调度队列
¥Sharing dispatch queues between modules
methodQueue
方法将在模块初始化时调用一次,然后由 React Native 保留,因此不需要自己保留对队列的引用,除非你希望在模块中使用它。但是,如果你希望在多个模块之间共享相同的队列,那么你将需要确保为每个模块保留并返回相同的队列实例。¥The
methodQueue
method will be called once when the module is initialized, and then retained by React Native, so there is no need to keep a reference to the queue yourself, unless you wish to make use of it within your module. However, if you wish to share the same queue between multiple modules then you will need to ensure that you retain and return the same queue instance for each of them.
依赖注入
¥Dependency Injection
React Native 将自动创建并初始化任何已注册的原生模块。但是,你可能希望创建并初始化自己的模块实例,例如注入依赖。
¥React Native will create and initialize any registered native modules automatically. However, you may wish to create and initialize your own module instances to, for example, inject dependencies.
你可以通过创建一个实现 RCTBridgeDelegate
协议的类、使用委托作为参数初始化 RCTBridge
并使用已初始化的桥初始化 RCTRootView
来实现此目的。
¥You can do this by creating a class that implements the RCTBridgeDelegate
Protocol, initializing an RCTBridge
with the delegate as an argument and initialising a RCTRootView
with the initialized bridge.
id<RCTBridgeDelegate> moduleInitialiser = [[classThatImplementsRCTBridgeDelegate alloc] init];
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:moduleInitialiser launchOptions:nil];
RCTRootView *rootView = [[RCTRootView alloc]
initWithBridge:bridge
moduleName:kModuleName
initialProperties:nil];
导出 Swift
¥Exporting Swift
Swift 不支持宏,因此在 React Native 中向 JavaScript 公开原生模块及其方法需要更多设置。然而,它的工作原理相对相同。假设你有相同的 CalendarModule
但作为 Swift 类:
¥Swift doesn't have support for macros, so exposing native modules and their methods to JavaScript inside React Native requires a bit more setup. However, it works relatively the same. Let's say you have the same CalendarModule
but as a Swift class:
// CalendarModule.swift
@objc(CalendarModule)
class CalendarModule: NSObject {
@objc(addEvent:location:date:)
func addEvent(_ name: String, location: String, date: NSNumber) -> Void {
// Date is ready to use!
}
@objc
func constantsToExport() -> [String: Any]! {
return ["someKey": "someValue"]
}
}
使用
@objc
修饰符来确保类和函数正确导出到 Objective-C 运行时非常重要。¥It is important to use the
@objc
modifiers to ensure the class and functions are exported properly to the Objective-C runtime.
然后创建一个私有实现文件,该文件将向 React Native 注册所需的信息:
¥Then create a private implementation file that will register the required information with React Native:
// CalendarModuleBridge.m
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(CalendarModule, NSObject)
RCT_EXTERN_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(nonnull NSNumber *)date)
@end
对于那些刚接触 Swift 和 Objective-C 的人来说,每当你 在 iOS 项目中混合两种语言 时,你还需要一个额外的桥接文件(称为桥接标头),以将 Objective-C 文件公开给 Swift。如果你通过 Xcode File>New File
菜单选项将 Swift 文件添加到应用中,Xcode 将为你创建此头文件。你需要在此头文件中导入 RCTBridgeModule.h
。
¥For those of you new to Swift and Objective-C, whenever you mix the two languages in an iOS project, you will also need an additional bridging file, known as a bridging header, to expose the Objective-C files to Swift. Xcode will offer to create this header file for you if you add your Swift file to your app through the Xcode File>New File
menu option. You will need to import RCTBridgeModule.h
in this header file.
// CalendarModule-Bridging-Header.h
#import <React/RCTBridgeModule.h>
你还可以使用 RCT_EXTERN_REMAP_MODULE
和 _RCT_EXTERN_REMAP_METHOD
更改要导出的模块或方法的 JavaScript 名称。欲了解更多信息,请参阅 RCTBridgeModule
。
¥You can also use RCT_EXTERN_REMAP_MODULE
and _RCT_EXTERN_REMAP_METHOD
to alter the JavaScript name of the module or methods you are exporting. For more information see RCTBridgeModule
.
制作第三方模块时很重要:仅 Xcode 9 及更高版本支持 Swift 静态库。为了在你在模块中包含的 iOS 静态库中使用 Swift 时构建 Xcode 项目,你的主应用项目必须包含 Swift 代码和桥接标头本身。如果你的应用项目不包含任何 Swift 代码,解决方法可以是一个空的 .swift 文件和一个空的桥接标头。
¥Important when making third party modules: Static libraries with Swift are only supported in Xcode 9 and later. In order for the Xcode project to build when you use Swift in the iOS static library you include in the module, your main app project must contain Swift code and a bridging header itself. If your app project does not contain any Swift code, a workaround can be a single empty .swift file and an empty bridging header.
保留的方法名称
¥Reserved Method Names
invalidate()
原生模块可以通过实现 invalidate()
方法来符合 iOS 上的 RCTInvalidating 协议。当原生桥无效时(即:在 devmode 重新加载时),此方法 可以被调用 。请根据需要使用此机制来对你的原生模块进行所需的清理。
¥Native modules can conform to the RCTInvalidating protocol on iOS by implementing the invalidate()
method. This method can be invoked when the native bridge is invalidated (ie: on devmode reload). Please use this mechanism as necessary to do the required cleanup for your native module.