react native 學習之 native modules
翻譯自https://facebook.github.io/react-native/docs/native-modules-ios.html
Native Modules
很多情況下,app需要使用原生的api,或者是用一些已經用OC、Swift或C++寫好的模塊,又或者需要寫出更高效率的、或多線程的代碼來支撐圖像處理、數據庫或其它高要求的需求。
React Native的設計當然是支持我們使用原生特性的,以使平臺本身的能力得以完全發揮。不過這相對來說是比較進階的功能,他們的存在雖然是必要的,但在日常開發中並不是必須要用到的。如果RN不支持摸個你想要用到的原生特性,你可以自己做支持。
這是一個高階教程,介紹了如何搭建一個原生模塊。閱讀者需要對OC或Swift以及一些核心原生模塊(eg.Foundation, UIKit)有一定的了解。
iOS Calendar Module Example
本教程以iOS Calendar Api為例。我們要通過JavaScript來使用iOS calendar。
一個原生模塊,就是一個實現了RCTBridgeModule協議的Objective-C類。RCT是ReaCT的縮寫。
// 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格式的對象類型,如:
- string (
NSString
) - number (
NSInteger
,float
,double
,CGFloat
,NSNumber
) - boolean (
BOOL
,NSNumber
) - array (
NSArray
) of any types from this list - object (
NSDictionary
) with string keys and values of any type from this list - function (
RCTResponseSenderBlock
)
同時也支持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對象。
react native 學習之 native modules