翻譯自https://facebook.github.io/react-native/docs/native-modules-ios.htmlhtml
Native Modulesreact
不少狀況下,app須要使用原生的api,或者是用一些已經用OC、Swift或C++寫好的模塊,又或者須要寫出更高效率的、或多線程的代碼來支撐圖像處理、數據庫或其它高要求的需求。ios
React Native的設計固然是支持咱們使用原生特性的,以使平臺自己的能力得以徹底發揮。不過這相對來講是比較進階的功能,他們的存在雖然是必要的,但在平常開發中並非必需要用到的。若是RN不支持摸個你想要用到的原生特性,你能夠本身作支持。git
這是一個高階教程,介紹瞭如何搭建一個原生模塊。閱讀者須要對OC或Swift以及一些核心原生模塊(eg.Foundation, UIKit)有必定的瞭解。github
iOS Calendar Module Example數據庫
本教程以iOS Calendar Api爲例。咱們要經過JavaScript來使用iOS calendar。react-native
一個原生模塊,就是一個實現了RCTBridgeModule協議的Objective-C類。RCT是ReaCT的縮寫。api
// CalendarManager.h #import <React/RCTBridgeModule.h> @interface CalendarManager : NSObject <RCTBridgeModule> @end
除了要實現RCTBridgeModule協議以外,還須要執行RCT_EXPORT_MODULE()宏指令,它接受的第一個參數,表示這個模塊在JavaScript中使用時的引用名。若是沒有傳這個參數,那默認OC的類名就是js中的引用名。數組
// CalendarManager.m @implementation CalendarManager // To export a module named CalendarManager RCT_EXPORT_MODULE(); // This would name the module AwesomeCalendarManager instead // RCT_EXPORT_MODULE(AwesomeCalendarManager); @end
若是須要在js中使用CalendarManager中的方法,須要使用RCT_EXPORT_METHOD()宏指令,將方法暴露出去。數據結構
#import "CalendarManager.h" #import <React/RCTLog.h> @implementation CalendarManager RCT_EXPORT_MODULE(); RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location) { RCTLogInfo(@"Pretending to create an event %@ at %@", name, location); }
暴露出去以後,在js文件中調用該方法的方式以下:
import { NativeModules } from 'react-native'; var CalendarManager = NativeModules.CalendarManager; CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey');
注:JavaScript中的方法名稱
暴露到JavaScript中的原生方法的方法名就是原生方法名第一個冒號前的內容。另外,RN定義了一個宏指令RCT_REMAP_METHOD(),能夠用來制定方法在JavaScript中的方法名。當原生代碼中暴露出去的不一樣的方法的方法名,第一個逗號前有相同的內容時,在JavaScript中方法名就會衝突,這個宏指令就用得上了。
暴露的原生方法的返回值類型只能是void,由於React Native的bridge是異步的,因此向JavaScript傳遞原生方法的調用結果的方式只能經過回調函數或註冊事件的方式。
Argument Types
RCT_EXPORT_METHOD支持全部標準的JSON格式的對象類型,如:
NSString
)NSInteger
, float
, double
, CGFloat
, NSNumber
)BOOL
, NSNumber
)NSArray
) of any types from this listNSDictionary
) with string keys and values of any type from this listRCTResponseSenderBlock
)同時也支持RCTConvert class支持的類型。RCTConvert的helper functions接收JSON值,將其轉化爲Objective-C的類型或類。
在咱們CalendarManager的例子中,咱們想要將日期傳給遠勝方法,可是咱們不能直接傳js的Date類型的對象,咱們須要將data類型的對象轉換成字符串或數字。原生方法能夠這樣寫:
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(nonnull NSNumber *)secondsSinceUnixEpoch) { NSDate *date = [RCTConvert NSDate:secondsSinceUnixEpoch]; }
或者這樣:
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(NSString *)ISO8601DateString) { NSDate *date = [RCTConvert NSDate:ISO8601DateString]; }
不過咱們也可使用自動類型轉換,省去手動轉換的步驟:
RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(NSDate *)date) { // Date is ready to use! }
在js中調用的方式以下:
CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey', date.getTime()); // passing date as number of seconds since Unix epoch // or CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey', date.toISOString()); // passing date as ISO-8601 string
這兩種調用都會使原生髮放獲得正確的NSDate類型的對象。若是是一個不合法的類型,你會看到紅盒子的錯誤信息。
若是CalendarManager.addEvent方法變得愈來愈複雜,參數的數量會愈來愈多,並且不是每個參數都是必要的。這時候就能夠考慮,經過字典的形式傳入參數。以下:
#import <React/RCTConvert.h> RCT_EXPORT_METHOD(addEvent:(NSString *)name details:(NSDictionary *)details) { NSString *location = [RCTConvert NSString:details[@"location"]]; NSDate *time = [RCTConvert NSDate:details[@"time"]]; ... }
在JavaScript中調用:
CalendarManager.addEvent('Birthday Party', { location: '4 Privet Drive, Surrey', time: date.getTime(), description: '...' })
注:關於array和map
Objective-C不會限制這兩種數據結構裏的內容的類型。若是原生模塊須要一個字符串的數組,而在js中調用的時候,傳入了包含數字和字符串的數組,咱們在原生方法中會得到既有NSNumber又有NSString類型的NSArray。對於數組,RCTConvert提供了一些約束類型的數據結構永遠方法的聲明中,例如NSStringArray,UIColorArray。對於maps,開發者須要利用RCTConvert的方法分別取去檢查裏面的值類型。
Callbacks
原生模塊能夠接收一個特別的參數:一個js回調函數,做爲原生函數執行過程當中或執行結束後,向js返回結果並經過js進行進一步處理的方法。
RCT_EXPORT_METHOD(findEvents:(RCTResponseSenderBlock)callback) { NSArray *events = ... callback(@[[NSNull null], events]); }
RCTResponseSenderBlock只接收一個參數,一個傳給js回調函數的參數數組。在咱們的例子中,咱們的第一個參數是錯誤信息,做爲回調函數參數數組的第一個元素。
CalendarManager.findEvents((error, events) => { if (error) { console.error(error); } else { this.setState({events: events}); } })
一個原生模塊,應該在方法中當即調用回調函數。也能夠將回調函數存下來以後再調用,這經常用在委託中,RCTAlertManager就是一個例子。若是回調函數不被出發,將會形成內存泄漏。若是onSuccess和onFail回調同時存在,只應該調用其中一個。
若是想要傳遞錯誤信息對象到js中,用RCTUtils.h提供的RCTMakeError。目前它向js傳入了一個error-shaped字典,不過將來會自動生成真正的js的Error對象。