1. 程式人生 > >ReactNative: 將自定義的ReactNative元件製作成第三方庫的詳細流程(製作-->釋出)

ReactNative: 將自定義的ReactNative元件製作成第三方庫的詳細流程(製作-->釋出)

一、簡介

在講本篇博文之前,需要你熟知怎麼自定義ReactNative元件,然後才好學習將自定義的ReactNative元件製作成第三方庫。本文中的自定義的ReactNative元件LoginManager API 源自上篇文章,所以需要先看一下上篇博文。言歸正傳,ReactNative的確提供了一個非常便捷的方式來擴充套件Native模組。如果要把模組做成第三方元件的話,還有一些工作要做:首先以一個靜態庫工程來編譯模組程式碼,提供JavaScript的封裝,最後建立Package.json來支援node的引用。在步驟開始之前,我先把上篇文章中建立Native原生模組的LoginManager API元件的類貼出來,如下所示:

LoginManager.h

//
//  LoginManager.h
//  RNDemo
//
//  Created by 夏遠全 on 2020/1/16.
//  Copyright © 2020 Facebook. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <React/RCTUtils.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTBridgeModule.h>

NS_ASSUME_NONNULL_BEGIN

//OC中定義一個列舉並匯出
typedef NS_ENUM(NSInteger, MoveDiretion){
    MoveDiretionNone,
    MoveDiretionLeft,
    MoveDiretionRight,
    MoveDiretionBottom,
    MoveDiretionTop
};

@interface LoginManager : NSObject<RCTBridgeModule>

@end

NS_ASSUME_NONNULL_END
View Code

LoginManager.m

//
//  LoginManager.m
//  RNDemo
//
//  Created by 夏遠全 on 2020/1/16.
//  Copyright © 2020 Facebook. All rights reserved.
//

#import "LoginManager.h"

#ifdef DEBUG
#define NSLog(format, ...) printf("[%s] %s [第%d行] %s\n", __TIME__, __FUNCTION__, __LINE__, [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
#else
#define NSLog(format, ...)
#endif

@implementation LoginManager

//初始化, 新增螢幕旋轉監聽者
-(instancetype)init {
    self = [super init];
    if (self) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
    }
    return self;
}

//匯出模組類
RCT_EXPORT_MODULE();


//重對映,auth為code方法的新名稱
RCT_REMAP_METHOD(auth,code:(NSString *)account{
                      NSLog(@"%s---獲取驗證----",__func__);
                      NSLog(@"account----%@",account);
                  });

//login為方法名
RCT_EXPORT_METHOD(login:(NSString *)account password:(NSString *)password{
                      NSLog(@"%s---登入賬號----",__func__);
                      NSLog(@"account----%@",account);
                      NSLog(@"password----%@",password);
                  });

//logout為方法名
RCT_EXPORT_METHOD(logout:(NSString *)account{
                      NSLog(@"%s---退出賬號----",__func__);
                      NSLog(@"account----%@",account);
                  });

//設定普通的回撥函式
//fetchUserInfoWithToken為方法名,successCallback為成功回撥,failureCallback為失敗回撥
RCT_EXPORT_METHOD(fetchUserInfoWithToken:(NSString *)token success:(RCTResponseSenderBlock)successCallback failure:(RCTResponseErrorBlock)failureCallback
    {
            if(token.length>0 && successCallback){
                  successCallback(@[
                                    @"account = xiayuanquan",
                                    @"password = 123456",
                                    [NSString stringWithFormat:@"token = %@",token]
                                  ]);
            }
            else{
                  if(failureCallback){
                        failureCallback(
                                          [[NSError alloc] initWithDomain:NSOSStatusErrorDomain
                                                                     code:404
                                                                 userInfo: @{NSLocalizedDescriptionKey: @"token exception"}]
                                        );
                  }
            }
    });


//設定非同步處理的回撥函式
//sendMessage為方法名,successCallback為成功回撥,failureCallback為失敗回撥
RCT_EXPORT_METHOD(sendMessage:(NSString *)message success:(RCTPromiseResolveBlock)successCallback failure:(RCTPromiseRejectBlock)failureCallback
    {
            if(message.length>0 && successCallback){
                  successCallback(@"傳送成功!");
            }
            else{
                  if(failureCallback){
                      failureCallback(@"300",@"傳送失敗",nil);
                  }
            }
    });


//重寫constantsToExport, 列舉常量匯出
- (NSDictionary<NSString *, id> *)constantsToExport {
   return @{
        @"MoveDiretionNone": @(MoveDiretionNone),
        @"MoveDiretionLeft": @(MoveDiretionLeft),
        @"MoveDiretionRight": @(MoveDiretionRight),
        @"MoveDiretionBottom": @(MoveDiretionBottom),
        @"MoveDiretionTop": @(MoveDiretionTop)
   };
}

//定義一個移動方法,根據傳入的列舉值移動
//move為方法名
RCT_EXPORT_METHOD(move:(MoveDiretion)moveDiretion{
                    switch(moveDiretion){
                        case MoveDiretionNone:
                             NSLog(@"仍保持原始位置 --- MoveDiretionNome");
                        break;
                        case MoveDiretionLeft:
                             NSLog(@"向左邊移動位置 --- MoveDiretionLeft");
                        break;
                        case MoveDiretionRight:
                             NSLog(@"向右邊移動位置 --- MoveDiretionRight");
                        break;
                        case MoveDiretionBottom:
                             NSLog(@"向下邊移動位置 --- MoveDiretionBottom");
                        break;
                        case MoveDiretionTop:
                             NSLog(@"向上邊移動位置 --- MoveDiretionTop");
                        break;
                    }
                 });


//可以重寫佇列,給當前模組類指定自定義的序列佇列。若不指定,則系統預設會給當前模組類隨機分配一個序列佇列。
//這個方法一旦重寫。當前模組的所有方法均會在該自定義的序列佇列中非同步執行
-(dispatch_queue_t)methodQueue{
    return dispatch_queue_create("com.facebook.ReactNative.LoginManagerQueue", DISPATCH_QUEUE_SERIAL);
}

//定義一個方法,獲取執行緒和佇列資訊
//thread為方法名
RCT_EXPORT_METHOD(thread:(BOOL)newQueue{
                  
              const char *queueName = dispatch_queue_get_label([self methodQueue]);
              NSLog(@"當前執行緒1 ------- %@-----%s", [NSThread currentThread], queueName);
              
              if(newQueue){
                  
                  dispatch_async(dispatch_get_main_queue(), ^{
                      const char *queueName2 = dispatch_queue_get_label(dispatch_get_main_queue());
                      NSLog(@"當前執行緒2 ------- %@ -------%s", [NSThread currentThread], queueName2);
                  });
          
                  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                      const char *queueName3 = dispatch_queue_get_label(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
                      NSLog(@"當前執行緒3 ------- %@-------%s", [NSThread currentThread], queueName3);
                  });
              }
          });


//獲取當前螢幕的尺寸
static NSDictionary *Dimensions(){
    CGFloat width = MIN(RCTScreenSize().width, RCTScreenSize().height);
    CGFloat height = MAX(RCTScreenSize().width, RCTScreenSize().height);
    CGFloat scale = RCTScreenScale();
    if(UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)){
        width = MAX(RCTScreenSize().width, RCTScreenSize().height);
        height = MIN(RCTScreenSize().width, RCTScreenSize().height);
    }
    return @{
        @"width": @(width),
        @"height": @(height),
        @"scale": @(scale)
    };
}
//定義一個方法,獲取螢幕資訊
//getDimensions為方法名
RCT_EXPORT_METHOD(getDimensions:(RCTResponseSenderBlock)callback{
    if (callback) {
        callback(@[[NSNull null], Dimensions()]);
    }
});

//監聽方法,使用RCTEventDispatcher的eventDispatcher呼叫sendDeviceEventWithName函式傳送事件資訊
//可以將事件名稱作為常量匯出,提供給ReactNative中使用,也即新增到constantsToExport方法中的字典中即可。類似上面的列舉。
@synthesize bridge = _bridge;
-(void)orientationDidChange:(NSNotification *)notification {
  [_bridge.eventDispatcher sendDeviceEventWithName:@"orientationDidChange" body:@{
      @"orientation": UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation) ? @"Landscape": @"Portrait",
      @"Dimensions": Dimensions()}
   ];
}

//移除監聽者
-(void)dealloc{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

@end
View Code

RCTConvert+MoveDiretion.h

//
//  RCTConvert+MoveDiretion.h
//  RNDemo
//
//  Created by 夏遠全 on 2020/1/17.
//  Copyright © 2020 Facebook. All rights reserved.
//

#import <React/RCTConvert.h>

NS_ASSUME_NONNULL_BEGIN

@interface RCTConvert (MoveDiretion)

@end

NS_ASSUME_NONNULL_END
View Code

RCTConvert+MoveDiretion.m

//
//  RCTConvert+MoveDiretion.m
//  RNDemo
//
//  Created by 夏遠全 on 2020/1/17.
//  Copyright © 2020 Facebook. All rights reserved.
//

#import "RCTConvert+MoveDiretion.h"
#import "LoginManager.h"

@implementation RCTConvert (MoveDiretion)

//給RCTConvert類新增擴充套件,這樣在模組方法呼叫中使用常量匯出的列舉值,通訊到Native中時,會從整型自動轉換為定義的列舉型別
RCT_ENUM_CONVERTER(MoveDiretion,(@{
   @"MoveDiretionNone": @(MoveDiretionNone),
   @"MoveDiretionLeft": @(MoveDiretionLeft),
   @"MoveDiretionRight": @(MoveDiretionRight),
   @"MoveDiretionBottom": @(MoveDiretionBottom),
   @"MoveDiretionTop": @(MoveDiretionTop),
}), MoveDiretionNone, integerValue)

@end
View Code

 

二、步驟

1、使用xcode建立一個名為LoginManager的靜態庫。

2、開啟靜態庫,將上面貼出來的已經實現好了的Native模組LoginManager API元件的類全部拷貝進去或進行替換。

3、選擇Build Settings,配置Header Search Paths路徑。

6、開啟終端,初始化一個新的RN專案,隨意設定一個名稱為:LoginManagerTestProject。(本人安裝的ReactNative版本較低)

//初始化
react-native init LoginManagerTestProject --version 0.44.3

7、進入目錄node_modules。

//進入node_modules
cd LoginManagerTestProject/node_modules

8、建立要製作的第三庫資料夾,指定名稱為:react-native-login-manager。

//建立第三方名稱
mkdir react-native-login-manager

9、進入這個第三方庫資料夾。

//進入庫檔案
cd react-native-login-manager

10、建立ios資料夾。

//建立ios檔案
mkdir ios

11、將之前建立的靜態庫中的根目錄下的檔案全部copy到這個RN工程LoginManagerTestProject/node_modules/react-native-login-manager/ios目錄下。目錄結構如下:

12、使用xcode開啟這個RN工程LoginManagerTestProject,將靜態庫LoginManager的.xcodeproj檔案 拖到 RN工程LoginManagerTestProject的Libraries檔案下。

13、手動新增libLoginManager.a靜態包,然後編譯,如果編譯不出錯,則success。

14、使用終端或者webStorm等程式設計工具進入到RN工程LoginManagerTestProject/node_modules/react-native-login-manager目錄下,建立index.js檔案,它是整個原生模組的入口,我們這裡只是將原生LoginManager模組類進行匯出。此時也就完成了第三方庫製作的第一步了,僅僅可以自己使用。 (還需要釋出到npm上,分享給大家安裝使用)

 

三、測試 

好了,咱先自己測試一下,看看行不行。結果是行的,