有時(shí)一個(gè)應(yīng)用程序需要訪問(wèn)平臺(tái) API,React Native 并沒(méi)有相應(yīng)的封裝器。也許你想重用現(xiàn)有的一些 Objective——C 或 C++ 代碼,無(wú)需在 JavaScript 上重新實(shí)現(xiàn)?;蛘邔懸恍└咝阅?,多線程的代碼,如圖像處理、網(wǎng)絡(luò)堆棧,數(shù)據(jù)庫(kù)或渲染。
我們?cè)O(shè)計(jì) React Native,這樣可以為你寫真正的本地代碼,并且能夠訪問(wèn)整個(gè)平臺(tái)。這是一個(gè)更高級(jí)的特性,且我們并不期望它成為通常開(kāi)發(fā)過(guò)程的一部分,但是它的存在是至關(guān)重要的。如果 React Native 不支持你需要的本地特性,那么你應(yīng)該能夠自己構(gòu)建它。
這是一個(gè)更高級(jí)的指南,展示了如何構(gòu)建一個(gè)本地模塊。它假設(shè)讀者知道 Objective-C(Swift 還沒(méi)有支持)和核心庫(kù)(Foundation,UIKit)。
本指南將使用 iOS 日歷 API 的例子。假設(shè)我們希望能夠從 JavaScript 訪問(wèn) iOS 日歷。
Native 模塊只是一個(gè) Objectve-C 類,實(shí)現(xiàn)了 RCTBridgeModule 協(xié)議。如果你想知道,RCT 是 ReaCT 的一個(gè)簡(jiǎn)稱。
// CalendarManager.h
#import "RCTBridgeModule.h"
#import "RCTLog.h"
@interface CalendarManager : NSObject <RCTBridgeModule>
@end
React Native 不會(huì)向 JavaScript 公開(kāi)任何 CalendarManager 方法,除非有明確的要求。幸運(yùn)的是有了 RCT_EXPORT,這會(huì)非常簡(jiǎn)單:
// CalendarManager.m
@implementation CalendarManager
- (void)addEventWithName:(NSString *)name location:(NSString *)location
{
RCT_EXPORT();
RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
}
@end
現(xiàn)在從你的 JavaScript 文件中,你可以像這樣調(diào)用方法:
var CalendarManager = require('NativeModules').CalendarManager;
CalendarManager.addEventWithName('Birthday Party', '4 Privet Drive, Surrey');
注意,導(dǎo)出的方法名稱是從 Objective-C 選擇器的第一部分中生成的。有時(shí)它會(huì)產(chǎn)生一個(gè)非慣用的 JavaScript 名稱(就像在我們的例子中的那個(gè))。你可以通過(guò)為 RCT_EXPORT 提供一個(gè)可選參數(shù)更改名字,如 RCT_EXPORT(addEvent)。
方法返回的類型應(yīng)該是 void。React Native 橋是異步的,所以向 JavaScript 傳遞結(jié)果的唯一方法是使用回調(diào)或 emitting 事件(見(jiàn)下文)。
React Native 支持多種參數(shù)類型,可以從 JavaScript 代碼傳遞到 native 模塊:
NSString)NSInteger,float,double ,CGFloat,NSNumber)BOOL,NSNumber)NSArray) NSDictionary)RCTResponseSenderBlock)在我們的 CalendarManager 示例中,如果我們想把事件日期傳遞到 native,我們必須將它轉(zhuǎn)換成一個(gè)字符串或一個(gè)數(shù)字:
- (void)addEventWithName:(NSString *)name location:(NSString *)location date:(NSInteger)secondsSinceUnixEpoch
{
RCT_EXPORT(addEvent);
NSDate *date = [NSDate dateWithTimeIntervalSince1970:secondsSinceUnixEpoch];
}
隨著 CalendarManager.addEvent 方法變得越來(lái)越復(fù)雜,參數(shù)的數(shù)量將會(huì)增加。其中一些可能是可選的。在這種情況下對(duì)改變 API 一點(diǎn)來(lái)接受事件屬性的字典是值得考慮的,如:
#import "RCTConvert.h"
- (void)addEventWithName:(NSString *)name details:(NSDictionary *)details
{
RCT_EXPORT(addEvent);
NSString *location = [RCTConvert NSString:details[@"location"]]; // ensure location is a string
...
}
并且從 JavaScript 調(diào)用它:
CalendarManager.addEvent('Birthday Party', {
location: '4 Privet Drive, Surrey',
time: date.toTime(),
description: '...'
})
注意:關(guān)于數(shù)組和映射
React Ntive 沒(méi)有為這些結(jié)構(gòu)中值的類型提供任何擔(dān)保。你的 native 模塊可能期望一個(gè)字符串?dāng)?shù)組,但如果 JavaScript 調(diào)用你的包含數(shù)字和字符串?dāng)?shù)組的方法,你會(huì)得到帶有
NSNumber和NSString的NSArray。檢查數(shù)組/映射值類型是開(kāi)發(fā)人員的責(zé)任 (助手方法見(jiàn)RCTConvert)。
警告
本節(jié)比其他更具有實(shí)驗(yàn)性,圍繞回調(diào)我們沒(méi)有得到一組最佳實(shí)踐。
Native 模塊還支持一種特殊的參數(shù)——回調(diào)。在大多數(shù)情況下它是用來(lái)向 JavaScript 提供函數(shù)調(diào)用結(jié)果的。
- (void)findEvents:(RCTResponseSenderBlock)callback
{
RCT_EXPORT();
NSArray *events = ...
callback(@[[NSNull null], events]);
}
RCTResponseSenderBlock 只接受一個(gè)參數(shù)——參數(shù)的數(shù)組傳遞給 JavaScript 的回調(diào)。在本例中,我們使用節(jié)點(diǎn)的慣例來(lái)為 error 和其他的——函數(shù)的結(jié)果設(shè)置第一個(gè)參數(shù)。
CalendarManager.findEvents((error, events) => {
if (error) {
console.error(error);
} else {
this.setState({events: events});
}
})
Native 模塊應(yīng)該只調(diào)用它的回調(diào)一次。然而,它可以將回調(diào)作為 ivar 存儲(chǔ)并稍后調(diào)用回調(diào)。這種模式通常用于包裝需要委托的 iOS 的 APIs。請(qǐng)看 RCTAlertManager。
如果你想向 JavaScript 傳遞 error ——如對(duì)象,使用 RCTUtils.h 的 RCTMakeError。
Native 模塊應(yīng)該沒(méi)有任何關(guān)于什么線程正在被調(diào)用的假設(shè)。React Native 在一個(gè)單獨(dú)的串行 GCD 隊(duì)列中調(diào)用 native 模塊方法,但這是一個(gè)實(shí)現(xiàn)細(xì)節(jié),可能會(huì)改變。如果 native 模塊需要調(diào)用 main-thread-only iOS API,它應(yīng)該在主隊(duì)列安排操作:
- (void)addEventWithName:(NSString *)name callback:(RCTResponseSenderBlock)callback
{
RCT_EXPORT(addEvent);
dispatch_async(dispatch_get_main_queue(), ^{
// Call iOS API on main thread
...
// You can invoke callback from any thread/queue
callback(@[...]);
});
}
同樣的方法,如果操作要很長(zhǎng)時(shí)間才能完成,native 模塊不應(yīng)該阻塞。使用 dispatch_async 在后臺(tái)隊(duì)列中安排耗費(fèi)大的工作是一個(gè)好主意。
Native 模塊可以在運(yùn)行時(shí)向 JavaScript 導(dǎo)出立即可用的常量。導(dǎo)出一些初始數(shù)據(jù)是有用的,否則這些初始數(shù)據(jù)需要往返的橋梁。
- (NSDictionary *)constantsToExport
{
return @{ @"firstDayOfTheWeek": @"Monday" };
}
JavaScript 能夠立即使用這些值:
console.log(CalendarManager.firstDayOfTheWeek);
注意,只有在初始化時(shí)常量才能被導(dǎo)出,所以如果你在運(yùn)行時(shí)改變了 constantsToExport 的值,它不會(huì)影響 JavaScript 環(huán)境。
Native 模塊可以在不被直接調(diào)用的情況下向 JavaScript 發(fā)送事件信號(hào)。最簡(jiǎn)單的方法是使用 eventDispatcher:
#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
@implementation CalendarManager
@synthesize bridge = _bridge;
- (void)calendarEventReminderReceived:(NSNotification *)notification
{
NSString *eventName = notification.userInfo[@"name"];
[self.bridge.eventDispatcher sendAppEventWithName:@"EventReminder"
body:@{@"name": eventName}];
}
@end
JavaScript 代碼可以訂閱這些事件:
var subscription = DeviceEventEmitter.addListener(
'EventReminder',
(reminder) => console.log(reminder.name)
);
...
// Don't forget to unsubscribe
subscription.remove();
更多的向 JavaScript 發(fā)送事件的例子,請(qǐng)看 RCTLocationObserver。