RN與原生互動(二)——資料傳遞

不變旋律發表於2018-06-13

我的上篇文章中簡單介紹了RN與原生基本的頁面跳轉,本篇主要總結RN和原生之間的資料傳遞方式,講解RN和原生端之間如何互相傳遞資料。

Demo地址:DataTransfer

RN與原生互動(二)——資料傳遞

RN向原生傳遞資料

在上一篇文章中已經說明了怎樣分別在iOS和Android端建立module類,怎樣使用。這裡不再贅述。RN向原生傳遞資料需要我們在module類中定義相關方法,方法引數即為RN傳遞過來的資料,原生端根據引數做相應處理。

傳遞字串

iOS程式碼如下:

/// RN向原生傳遞字串
RCT_EXPORT_METHOD(getStringFromReactNative:(NSString *)s) {
  NSString *msg = [NSString stringWithFormat:@"RN傳遞過來的字串:%@", s];
  [self showAlert:msg];
}
複製程式碼

Android如下:

/**
     * RN向原生傳遞字串
     * @param s
     */
    @ReactMethod
    public void getStringFromReactNative(String s) {
        Toast.makeText(mContext, s, Toast.LENGTH_SHORT).show();
    }
複製程式碼

傳遞整數或其它數字型別與此類似。

傳遞字典

這個很關鍵,iOS端接收資料型別還是NSDictionary型別,但Android端接收引數可不是Map、HashMap之類的,而是ReadableMap,這裡要劃重點。

/**
     * RN向原生傳遞字典。這裡原生端接收RN傳過來的字典型別是ReadableMap
     * @param map
     */
    @ReactMethod
    public void getDictionaryFromRN(ReadableMap map) {
        System.out.print(map);
        Toast.makeText(mContext, "已收到字典資料", Toast.LENGTH_SHORT).show();
    }
複製程式碼

iOS端核心程式碼:

/// RN向原生傳遞字典
RCT_EXPORT_METHOD(getDictionaryFromRN:(NSDictionary *)dict) {
  NSLog(@"RN傳遞過來的字典:%@", dict);
  NSString *name = [dict objectForKey:@"title"];
  [self showAlert:name];
}
複製程式碼

傳遞陣列

iOS端接收引數型別可定義為NSArray,但Android端接收引數需要定義為ReadableArray,不能是java中的集合型別,如List、ArrayList是解析不到資料的。

Android程式碼:

@ReactMethod
    public void getArrayFromRN(ReadableArray array) {
        System.out.print(array);
        Toast.makeText(mContext, "已收到陣列資料", Toast.LENGTH_SHORT).show();
    }
複製程式碼

原生向RN回撥資料

以Android端為例,RN在呼叫以下方法時可以通過回撥獲取到原生端返回的資料。

/**
     * 原生通過回撥的形式向RN端傳遞string
     * @param callback
     */
    @ReactMethod
    public void passStringBackToRN(Callback callback) {
        callback.invoke("This is a string from Native");
    }

    /**
     * 原生通過回撥的形式向RN端傳遞字典。這裡傳出去的字典型別必須是WritableMap,java中的Map、HashMap是不能傳遞到RN的
     * @param callback
     */
    @ReactMethod
    public void passDictionaryBackToRN(Callback callback) {
        WritableMap map = Arguments.createMap();
        map.putString("name", "小明");
        map.putInt("age", 20);
        map.putString("gender", "male");
        map.putBoolean("isGraduated", true);
        callback.invoke(map);
    }

    /**
     * 原生通過回撥的形式向RN端傳遞陣列。這裡傳出去的字典型別必須是WritableArray
     * @param callback
     */
    @ReactMethod
    public void passArrayBackToRN(Callback callback) {
        WritableArray array = Arguments.createArray();
        array.pushString("React Native");
        array.pushString("Android");
        array.pushString("iOS");
        callback.invoke(array);
    }

    @ReactMethod
    public void passPromiseBackToRN(String msg, Promise promise) {
        if (!msg.equals("")) {
            promise.resolve(true);
        } else {
            promise.reject("warning", "msg cannot be empty!");
        }
    }
複製程式碼

iOS端在回撥資料時一般使用RCTResponseSenderBlock,任何資料型別都以block形式返回,示例如下:

/// 回傳陣列到RN端
RCT_EXPORT_METHOD(passArrayBackToRN:(RCTResponseSenderBlock)block) {
  if (block) {
    NSArray *items = @[@"React Native", @"Android", @"iOS"];
    block(@[items]);
  }
}
複製程式碼

iOS端以promise形式返回資料與Android不同,Android端定義了一個Promise類,iOS端還是通過block形式給出回撥,使用RCTPromiseResolveBlock和RCTPromiseRejectBlock

/// 以promise形式回傳資料到RN端
RCT_EXPORT_METHOD(passPromiseBackToRN:(NSString *)msg resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
  if (![msg isEqualToString:@""]) {
    resolve(@(YES));
  } else {
    reject(@"warning", @"msg cannot be empty!", nil);
  }
}
複製程式碼

傳送事件

Android端核心程式碼:

public void sendEvent(String eventName) {
        String dataToRN = "這是發給RN的字串";
        mContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, dataToRN);
    }
複製程式碼

sendEvent方法定義在module類中,在需要傳送事件的地方呼叫sendEvent方法就可以將事件通知發給RN端。

RN端監聽Android端發來的通知如下,這裡假設事件名稱為“CustomEventName”

componentWillMount(){ 
       DeviceEventEmitter.addListener('CustomEventName', (e)=> {  
           console.log("接收到通知") ;
       });  
   }  
複製程式碼

iOS端與Android不同,不使用DeviceEventEmitter做監聽,而是用NativeEventEmitter。具體實現方案如下:

  1. 使Module類繼承RCTEventEmitter,重寫supportedEvents方法,在這個方法中宣告支援的事件名稱。
  2. 在Module類的init方法中使用NSNotificationCenter監聽iOS端要傳送事件的操作。
  3. 在NSNotification對應的通知方法中將事件傳送給RN。

核心程式碼:

+ (id)allocWithZone:(struct _NSZone *)zone {
  static DataTransferModule *sharedInstance = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sharedInstance = [super allocWithZone:zone];
  });
  return sharedInstance;
}

- (instancetype)init {
  self = [super init];
  if (self) {
    NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
    [defaultCenter removeObserver:self];
    [defaultCenter addObserver:self
                      selector:@selector(sendCustomEvent:)
                          name:@"sendCustomEventNotification"
                        object:nil];
  }
  return self;
}

/// 接收通知的方法,接收到通知後傳送事件到RN端。RN端接收到事件後可以進行相應的邏輯處理或介面跳轉
- (void)sendCustomEvent:(NSNotification *)notification {
  [self sendEventWithName:@"CustomEventName" body:@"這是發給RN的字串"];
}

/// 重寫方法,定義支援的事件集合
- (NSArray<NSString *> *)supportedEvents {
  return @["CustomEventName"];
}
複製程式碼

比如在iOS端我有個頁面A,點選頁面中的一個button需要關掉當前頁面並且傳送資料給RN端,使RN端接收到資料後做跳轉操作。那麼在A中button的點選方法如下:

- (void)buttonClicked:(id)sender {
  [[NSNotificationCenter defaultCenter] postNotificationName:@"sendCustomEventNotification" object:nil];
  [self dismissViewControllerAnimated:YES completion:nil];
}
複製程式碼

Module類中接收到通知後就將這個要做的操作傳送給RN了。RN端監聽方法如下:

const DataTransferModule = NativeModules.DataTransferModule;

componentDidMount() {
    let eventEmitter = new NativeEventEmitter(DataTransferModule);
      this.listener = eventEmitter.addListener("CustomEventName", (result) => {
        this.showAlert("獲取到事件通知" + result);
      })
  }
  
  componentWillUnmount() {
    this.listener && this.listener.remove();
  }
複製程式碼

相關文章