有時(shí)一個(gè)應(yīng)用程序需要訪問平臺(tái) API,React Native 并沒有相應(yīng)的封裝器。也許你想重用現(xiàn)有的一些 Objective——C 或 C++ 代碼,無需在 JavaScript 上重新實(shí)現(xiàn)?;蛘邔懸恍└咝阅埽嗑€程的代碼,如圖像處理、網(wǎng)絡(luò)堆棧,數(shù)據(jù)庫或渲染。
我們?cè)O(shè)計(jì) React Native,這樣可以為你寫真正的本地代碼,并且能夠訪問整個(gè)平臺(tái)。這是一個(gè)更高級(jí)的特性,且我們并不期望它成為通常開發(fā)過程的一部分,但是它的存在是至關(guān)重要的。如果 React Native 不支持你需要的本地特性,那么你應(yīng)該能夠自己構(gòu)建它。
這是一個(gè)更高級(jí)的指南,展示了如何構(gòu)建一個(gè)本地模塊。它假設(shè)讀者知道 Objective-C(Swift 還沒有支持)和核心庫(Foundation,UIKit)。
本指南將使用 iOS 日歷 API 的例子。假設(shè)我們希望能夠從 JavaScript 訪問 iOS 日歷。
Native 模塊只是一個(gè) Objectve-C 類,實(shí)現(xiàn)了 RCTBridgeModule
協(xié)議。如果你想知道,RCT 是 ReaCT 的一個(gè)簡稱。
// CalendarManager.h #import "RCTBridgeModule.h" #import "RCTLog.h" @interface CalendarManager : NSObject <RCTBridgeModule> @end
React Native 不會(huì)向 JavaScript 公開任何 CalendarManager
方法,除非有明確的要求。幸運(yùn)的是有了 RCT_EXPORT
,這會(huì)非常簡單:
// 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è))。你可以通過為 RCT_EXPORT
提供一個(gè)可選參數(shù)更改名字,如 RCT_EXPORT(addEvent)
。
方法返回的類型應(yīng)該是 void
。React Native 橋是異步的,所以向 JavaScript 傳遞結(jié)果的唯一方法是使用回調(diào)或 emitting 事件(見下文)。
React Native 支持多種參數(shù)類型,可以從 JavaScript 代碼傳遞到 native 模塊:
字符串型(NSString
)
數(shù)字型(NSInteger
,float
,double
,CGFloat
,NSNumber
)
布爾型(BOOL
,NSNumber
)
這個(gè)列表中任何類型的數(shù)組(NSArray
)
這個(gè)列表中任何類型的字符串鍵和值的映射(NSDictionary
)
函數(shù)(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
方法變得越來越復(fù)雜,參數(shù)的數(shù)量將會(huì)增加。其中一些可能是可選的。在這種情況下對(duì)改變 API 一點(diǎn)來接受事件屬性的字典是值得考慮的,如:
#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 沒有為這些結(jié)構(gòu)中值的類型提供任何擔(dān)保。你的 native 模塊可能期望一個(gè)字符串?dāng)?shù)組,但如果 JavaScript 調(diào)用你的包含數(shù)字和字符串?dāng)?shù)組的方法,你會(huì)得到帶有
NSNumber
和NSString
的NSArray
。檢查數(shù)組/映射值類型是開發(fā)人員的責(zé)任 (助手方法見RCTConvert
)。
警告
本節(jié)比其他更具有實(shí)驗(yàn)性,圍繞回調(diào)我們沒有得到一組最佳實(shí)踐。
Native 模塊還支持一種特殊的參數(shù)——回調(diào)。在大多數(shù)情況下它是用來向 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)的慣例來為 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)該沒有任何關(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(@[...]); }); }
同樣的方法,如果操作要很長時(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)。最簡單的方法是使用 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。
更多建議: