日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

React Native使用指南-原生模块

發布時間:2024/7/23 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 React Native使用指南-原生模块 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
有時候App需要訪問平臺API,但React Native可能還沒有相應的模塊封裝;或者你需要復用Objective-C、Swift或C++代碼,而不是用JavaScript重新實現一遍;又或者你需要實現某些高性能、多線程的代碼,譬如圖片處理、數據庫、或者各種高級擴展等等。

我們把React Native設計為可以在其基礎上編寫真正的原生代碼,并且可以訪問平臺所有的能力。這是一個相對高級的特性,我們并不認為它應當在日常開發的過程中經常出現,但具備這樣的能力是很重要的。如果React Native還不支持某個你需要的原生特性,你應當可以自己實現該特性的封裝。

本文是關于如何封裝原生模塊的高級向導,我們假設您已經具備Objective-C或者Swift,以及iOS核心庫(Foundation、UIKit)的相關知識。

iOS 日歷模塊演示

本向導將會用iOS日歷API作為示例。我們的目標就是在Javascript中可以訪問到iOS的日歷功能。

在React Native中,一個“原生模塊”就是一個實現了“RCTBridgeModule”協議的Objective-C類,其中RCT是ReaCT的縮寫。

// CalendarManager.h #import "RCTBridgeModule.h"@interface CalendarManager : NSObject <RCTBridgeModule> @end

為了實現RCTBridgeModule協議,你的類需要包含RCT_EXPORT_MODULE()宏。這個宏也可以添加一個參數用來指定在Javascript中訪問這個模塊的名字。如果你不指定,默認就會使用這個Objective-C類的名字。

// CalendarManager.m @implementation CalendarManagerRCT_EXPORT_MODULE();@end

你必須明確的聲明要給Javascript導出的方法,否則React Native不會導出任何方法。聲明通過RCT_EXPORT_METHOD()宏來實現:

RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location) {RCTLogInfo(@"Pretending to create an event %@ at %@", name, location); }

現在從Javascript里可以這樣調用這個方法:

var CalendarManager = require('react-native').NativeModules.CalendarManager; CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey');

注意: Javascript方法名

導出到Javascript的方法名是Objective-C的方法名的第一個部分。React Native還定義了一個RCT_REMAP_METHOD()宏,它可以指定Javascript方法名。當許多方法的第一部分相同的時候用它來避免在Javascript端的名字沖突。

橋接到Javascript的方法返回值類型必須是void。React Native的橋接操作是異步的,所以要返回結果給Javascript,你必須通過回調或者觸發事件來進行。(參見本文檔后面的部分)

參數類型

RCT_EXPORT_METHOD?支持所有標準JSON類型,包括:

  • string (NSString)
  • number (NSInteger,?float,?double,?CGFloat,?NSNumber)
  • boolean (BOOL,?NSNumber)
  • array (NSArray) 包含本列表中任意類型
  • map (NSDictionary) 包含string類型的鍵和本列表中任意類型的值
  • function (RCTResponseSenderBlock)

除此以外,任何RCTConvert類支持的的類型也都可以使用(參見RCTConvert了解更多信息)。RCTConvert還提供了一系列輔助函數,用來接收一個JSON值并轉換到原生Objective-C類型或類。

在我們的CalendarManager例子里,我們需要把事件的時間交給原生方法。我們不能在橋接通道里傳遞Date對象,所以需要把日期轉化成字符串或數字來傳遞。我們可以這么實現原生函數:

RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(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! }

在Javascript既可以這樣:

CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey', date.getTime()); // 把日期以unix時間戳形式傳遞

也可以這樣:

CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey', date.toISOString()); // 把日期以ISO-8601的字符串形式傳遞

兩個值都會被轉換為正確的NSDate類型。但如果提供一個不合法的值,譬如一個Array,則會產生一個“紅屏”報錯信息。

隨著CalendarManager.addEvent方法變得越來越復雜,參數的個數越來越多,其中有一些可能是可選的參數。在這種情況下我們應該考慮修改我們的API,用一個dictionary來存放所有的事件參數,像這樣:

#import "RCTConvert.h"RCT_EXPORT_METHOD(addEvent:(NSString *)name details:(NSDictionary *)details) {NSString *location = [RCTConvert NSString:details[@"location"]];NSDate *time = [RCTConvert NSDate:details[@"time"]];... }

然后在JS里這樣調用:

CalendarManager.addEvent('Birthday Party', {location: '4 Privet Drive, Surrey',time: date.toTime(),description: '...' })

注意: 關于數組和映射

Objective-C并沒有提供確保這些結構體內部值的類型的方式。你的原生模塊可能希望收到一個字符串數組,但如果JavaScript在調用的時候提供了一個混合number和string的數組,你會收到一個NSArray,里面既有NSNumber也有NSString。對于數組來說,RCTConvert提供了一些類型化的集合,譬如NSStringArray或者UIColorArray,你可以用在你的函數聲明中。對于映射而言,開發者有責任自己調用RCTConvert的輔助方法來檢測和轉換值的類型。

回調函數

警告

本章節內容目前還處在實驗階段,因為我們還并沒有太多的實踐經驗來處理回調函數。

原生模塊還支持一種特殊的參數——回調函數。它提供了一個函數來把返回值傳回給JavaScript。

RCT_EXPORT_METHOD(findEvents:(RCTResponseSenderBlock)callback) {NSArray *events = ...callback(@[[NSNull null], events]); }

RCTResponseSenderBlock只接受一個參數——傳遞給JavaScript回調函數的參數數組。在上面這個例子里我們用Node.js的常用習慣:第一個參數是一個錯誤對象(沒有發生錯誤的時候為null),而剩下的部分是函數的返回值。

CalendarManager.findEvents((error, events) => {if (error) {console.error(error);} else {this.setState({events: events});} })

原生模塊通常只應調用回調函數一次。但是,它可以保存callback并在將來調用。這在封裝那些通過“委托函數”來獲得返回值的iOS API時最為常見。RCTAlertManager中就屬于這種情況。

如果你想傳遞一個更接近Error類型的對象給Javascript,可以用RCTUtils.h提供的RCTMakeError函數。現在它僅僅是發送了一個和Error結構一樣的dictionary給Javascript,但我們考慮在將來版本里讓它產生一個真正的Error對象。

Promises

譯注:這一部分涉及到較新的js語法和特性,不熟悉的讀者建議先閱讀ES6的相關書籍和文檔。

原生模塊還可以使用promise來簡化代碼,搭配ES2016(ES7)標準的async/await語法則效果更佳。如果橋接原生方法的最后兩個參數是RCTPromiseResolveBlock和RCTPromiseRejectBlock,則對應的JS方法就會返回一個Promise對象。

我們把上面的代碼用promise來代替回調進行重構:

RCT_REMAP_METHOD(findEvents,resolver:(RCTPromiseResolveBlock)resolverejecter:(RCTPromiseRejectBlock)reject)) {NSArray *events = ...if (events) {resolve(events);} else {reject(error);} }

現在JavaScript端的方法會返回一個Promise。這樣你就可以在一個聲明了async的異步函數內使用await關鍵字來調用,并等待其結果返回。(雖然這樣寫著看起來像同步操作,但實際仍然是異步的,并不會阻塞執行來等待)。

async function updateEvents() {try {var events = await CalendarManager.findEvents();this.setState({ events });} catch (e) {console.error(e);} }updateEvents();

多線程

原生模塊不應對自己被調用時所處的線程做任何假設。React Native在一個獨立的串行GCD隊列中調用原生模塊的方法,但這屬于實現的細節,并且可能會在將來的版本中改變。通過實現方法- (dispatch_queue_t)methodQueue,原生模塊可以指定自己想在哪個隊列中被執行。具體來說,如果模塊需要調用一些必須在主線程才能使用的API,那應當這樣指定:

- (dispatch_queue_t)methodQueue {return dispatch_get_main_queue(); }

類似的,如果一個操作需要花費很長時間,原生模塊不應該阻塞住,而是應當聲明一個用于執行操作的獨立隊列。舉個例子,RCTAsyncLocalStorage模塊創建了自己的一個queue,這樣它在做一些較慢的磁盤操作的時候就不會阻塞住React本身的消息隊列:

- (dispatch_queue_t)methodQueue {return dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL); }

指定的methodQueue會被你模塊里的所有方法共享。如果你的方法中“只有一個”是耗時較長的(或者是由于某種原因必須在不同的隊列中運行的),你可以在函數體內用dispatch_async方法來在另一個隊列執行,而不影響其他方法:

RCT_EXPORT_METHOD(doSomethingExpensive:(NSString *)param callback:(RCTResponseSenderBlock)callback) {dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{// 在這里執行長時間的操作...// 你可以在任何線程/隊列中執行回調函數callback(@[...]);}); }

注意: 在模塊之間共享分發隊列

methodQueue方法會在模塊被初始化的時候被執行一次,然后會被React Native的橋接機制保存下來,所以你不需要自己保存隊列的引用,除非你希望在模塊的其它地方使用它。但是,如果你希望在若干個模塊中共享同一個隊列,則需要自己保存并返回相同的隊列實例;僅僅是返回相同名字的隊列是不行的。

導出常量

原生模塊可以導出一些常量,這些常量在JavaScript端隨時都可以訪問。用這種方法來傳遞一些靜態數據,可以避免通過bridge進行一次來回交互。

- (NSDictionary *)constantsToExport {return @{ @"firstDayOfTheWeek": @"Monday" }; }

Javascript端可以隨時同步地訪問這個數據:

console.log(CalendarManager.firstDayOfTheWeek);

但是注意這個常量僅僅在初始化的時候導出了一次,所以即使你在運行期間改變constantToExport返回的值,也不會影響到JavaScript環境下所得到的結果。

枚舉常量

用NS_ENUM定義的枚舉類型必須要先擴展對應的RCTConvert方法才可以作為函數參數傳遞。

假設我們要導出如下的NS_ENUM定義:

typedef NS_ENUM(NSInteger, UIStatusBarAnimation) {UIStatusBarAnimationNone,UIStatusBarAnimationFade,UIStatusBarAnimationSlide, };

你需要這樣來擴展RCTConvert類:

@implementation RCTConvert (StatusBarAnimation)RCT_ENUM_CONVERTER(UIStatusBarAnimation, (@{ @"statusBarAnimationNone" : @(UIStatusBarAnimationNone),@"statusBarAnimationFade" : @(UIStatusBarAnimationFade),@"statusBarAnimationSlide" : @(UIStatusBarAnimationSlide)},UIStatusBarAnimationNone, integerValue) @end

接著你可以這樣定義方法并且導出enum值作為常量:

- (NSDictionary *)constantsToExport {return @{ @"statusBarAnimationNone" : @(UIStatusBarAnimationNone),@"statusBarAnimationFade" : @(UIStatusBarAnimationFade),@"statusBarAnimationSlide" : @(UIStatusBarAnimationSlide) } };RCT_EXPORT_METHOD(updateStatusBarAnimation:(UIStatusBarAnimation)animationcompletion:(RCTResponseSenderBlock)callback)

你的枚舉現在會用上面提供的選擇器進行轉換(上面的例子中是integerValue),然后再傳遞給你導出的函數。

給Javascript發送事件

即使沒有被JavaScript調用,本地模塊也可以給JavaScript發送事件通知。最直接的方式是使用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 { NativeAppEventEmitter } = require('react-native');var subscription = NativeAppEventEmitter.addListener('EventReminder',(reminder) => console.log(reminder.name) ); ... // 千萬不要忘記忘記取消訂閱, 通常在componentWillUnmount函數中實現。 subscription.remove();

更多的給JavaScript發送事件的例子,參見RCTLocationObserver.

從Swift導出

Swift不支持宏,所以從Swift向React Native導出類和函數需要多做一些設置,但是大致與Objective-C是相同的。

假設我們已經有了一個一樣的CalendarManager,不過是用Swift實現的類:

// CalendarManager.swift@objc(CalendarManager) class CalendarManager: NSObject {@objc func addEvent(name: String, location: String, date: NSNumber) -> Void {// Date is ready to use!}}

注意: 你必須使用@objc標記來確保類和函數對Objective-C公開。

接著,創建一個私有的實現文件,并將必要的信息注冊到React Native中。

// CalendarManagerBridge.m #import "RCTBridgeModule.h"@interface RCT_EXTERN_MODULE(CalendarManager, NSObject)RCT_EXTERN_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(nonnull NSNumber *)date)@end

請注意,一旦你在IOS中混用2種語言, 你還需要一個額外的橋接頭文件,稱作“bridging header”,用來導出Objective-C文件給Swift。如果你是通過Xcode菜單中的File>New File來創建的Swift文件,Xcode會自動為你創建這個頭文件。在這個頭文件中,你需要引入RCTBridgeModule.h。

// CalendarManager-Bridging-Header.h #import "RCTBridgeModule.h"

同樣的,你也可以使用RCT_EXTERN_REMAP_MODULE和RCT_EXTERN_REMAP_METHOD來改變導出模塊和方法的JavaScript調用名稱。 了解更多信息,請參閱RCTBridgeModule.


本文轉自React Native中文網:http://reactnative.cn/docs/0.20/native-modules-ios.html#content

總結

以上是生活随笔為你收集整理的React Native使用指南-原生模块的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。