你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> ReactNative之原生模塊開發並發布

ReactNative之原生模塊開發並發布

編輯:IOS開發基礎

fileImgviewFile.jpg

前段時間做了個ReactNative的App,發現ReactNative中不少組件並不存在,所以還是需要自己對原生模塊進行編寫讓JS調用,正是因為在這個編寫過程中遇到不少問題,發覺了官網文檔中許多的不足。所以產生了寫一個實踐教程的想法,最終有了這麼一篇文章。

整篇文章主要以編寫一個原生模塊為例子,來講述了我們在編寫原生模塊所用到的一些知識,並且在整個例子中,配有了完整的實踐代碼,方便大家理解並調試。除了這些內容,文章還講述了我們如何將自己編寫的原生模塊發布到npm上分享給別人使用。希望能夠給大家帶來幫助,也希望大家將自己編寫的原生模塊分享出來。

示例代碼github地址:https://github.com/liuchungui/react-native-BGNativeModuleExample

准備工作:

創建ReactNative工程

我們需要先創建一個ReactNative工程,使用如下命令創建。

react native init TestProject

創建好工程之後,我們使用xcode打開TestProject/ios/下的iOS工程。

創建靜態庫,並將這個靜態庫手動鏈接到工程中

首先,我們在前面創建的ReactNative工程下的node_modules創建一個文件夾react-native-BGNativeModuleExample,然後我們在新創建的文件夾下再創建一個ios文件夾。

$ cd TestProject/node_modules
$ mkdir react-native-BGNativeModuleExample
$ cd react-native-BGNativeModuleExample
$ mkdir ios

然後,由於ReactNative的組件都是一個個靜態庫,我們發布到npm給別人使用的話,也需要建立靜態庫。我們使用Xcode建立靜態庫,取名為BGNativeModuleExample。建立之後,我們將創建的靜態庫中的文件全部copy到node_modules/react-native-BGNativeModuleExample/ios目錄下。

iOS文件目錄如下:

|____BGNativeModuleExample
| |____BGNativeModuleExample.h
| |____BGNativeModuleExample.m
|____BGNativeModuleExample.xcodeproj

最後,我們需要手動將這個靜態庫鏈接到工程中。

1、使用xcode打開創建的靜態庫,添加一行Header Search Paths,值為$(SRCROOT)/../../react-native/React,並設置為recursive。

7746cd07jw1f3h69rwj3oj212s0r7dm6.jpg

2、將BGNativeModuleExample靜態庫工程拖動到工程中的Library中。 

7746cd07jw1f3o30vl8wjj20xq0fawi3.jpg

3、選中 TARGETS => TestProject => Build Settings => Link Binary With Libraries,添加libBGNativeModuleExample.a這個靜態庫 

7746cd07jw1f3o2v5wrgyj212r0hgq72.jpg

到此,我們准備工作完成了。我們這裡這麼准備是有用意的,那就是模擬npm鏈接的過程,建立好了環境,避免了發布到npm上後別人使用找不到靜態庫的問題。

一、編寫原生模塊代碼

1、創建原生模塊

選中我們創建的BGNativeModuleExample靜態庫,然後在BGNativeModuleExample.h文件中導入RCTBridgeModule.h,讓BGNativeModuleExample類遵循RCTBridgeModule協議。

//BGNativeModuleExample.h文件的內容如下
#import #import "RCTBridgeModule.h"
@interface BGNativeModuleExample : NSObject @end

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

在這裡,我們指定了模塊的名字為BGNativeModuleExample。

RCT_EXPORT_MODULE(BGNativeModuleExample);

實現了RCTBridgeModule協議之後,我們就可以在js中如下獲取到我們創建的原生模塊。

import { NativeModules } from 'react-native';
var BGNativeModuleExample = NativeModules.BGNativeModuleExample;

需要注意的是,RCT_EXPORT_MODULE宏傳遞的參數不能是OC中的字符串。如果傳遞@“BGNativeModuleExample",那麼我們導出給JS的模塊名字其實是@"BGNativeModuleExample",使用BGNativeModuleExample就找不到了。在這裡,我們其實可以通過打印NativeModules來查找到我們創建的原生模塊。

2、為原生模塊添加方法

我們需要明確的聲明要給JS導出的方法,否則ReactNative不會導出任何方法。聲明通過RCT_EXPORT_METHOD()宏來實現:

RCT_EXPORT_METHOD(testPrint:(NSString *)name info:(NSDictionary *)info) {
  RCTLogInfo(@"%@: %@", name, info);
}

在JS中,我們可以這樣調用這個方法:

BGNativeModuleExample.testPrint("Jack", {
  height: '1.78m',
  weight: '7kg'
});

3、參數類型

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類型或類。

了解更多請點擊原生模塊。

4、回調函數

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

回調函數,在官方的文檔中是有上面的一個警告,不過在使用過程暫時未發現問題。在OC中,我們添加一個getNativeClass方法,將當前模塊的類名回調給JS。

RCT_EXPORT_METHOD(getNativeClass:(RCTResponseSenderBlock)callback) {
  callback(@[NSStringFromClass([self class])]);
}

在JS中,我們通過以下方式獲取到原生模塊的類名

BGNativeModuleExample.getNativeClass(name => {
  console.log("nativeClass: ", name);
});

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

5、Promises

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

我們通過Promises來實現原生模塊是否會響應方法,響應則返回YES,不響應則返回一個錯誤信息,代碼如下:

RCT_REMAP_METHOD(testRespondMethod,
                 name:(NSString *)name
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject) {
  if([self respondsToSelector:NSSelectorFromString(name)]) {
    resolve(@YES);
  }
  else {
    reject(@"-1001", @"not respond this method", nil);
  }
}

在JS中,我們有兩種方式調用,第一種是通過then....catch的方式:

BGNativeModuleExample.testRespondMethod("dealloc")
    .then(result => {
      console.log("result is ", result);
    })
    .catch(error => {
      console.log(error);
    });

第二種是通過try...catch來調用,與第一種相比,第二種會報警告”Possible Unhandled Promiss Rejection (id:0)“。

async testRespond() {
try {
  var result = BGNativeModuleExample.testRespondMethod("hell");
  if(result) {
    console.log("respond this method");
  }
} catch (e) {
  console.log(e);
}
  }

注意: 如果使用Promiss我們不需要參數,則在OC去掉name那一行就行了;如果需要多個參數,在name下面多加一行就行了,注意它們之間不需要添加逗號。

6、多線程

我們這裡操作的模塊沒有涉及到UI,所以專門建立一個串行的隊列給它使用,如下:

return dispatch_queue_create("com.liuchungui.demo", DISPATCH_QUEUE_SERIAL);

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

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

更多線程的操作細節可以參考:http://reactnative.cn/docs/0.24/native-modules-ios.html#content

7、導出常量

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

OC中,我們實現constantsToExport方法,如下:

- (NSDictionary *)constantsToExport {
  return @{ @"BGModuleName" : @"BGNativeModuleExample",
            TestEventName: TestEventName
            };
}

JS中,我們打印一下這個常量

console.log("BGModuleName value is ", BGNativeModuleExample.BGModuleName);

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

8、給JS發送事件

即使沒有被JS調用,本地模塊也可以給JS發送事件通知。最直接的方式是使用eventDispatcher。

在這裡,我們為了能夠接收到事件,我們開一個定時器,每一秒發送一次事件。

#import "BGNativeModuleExample.h"
#import "RCTEventDispatcher.h"
@implementation BGNativeModuleExample
@synthesize bridge = _bridge;
- (instancetype)init {
  if(self = [super init]) {
    [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(sendEventToJS) userInfo:nil repeats:YES];
  }
  return self;
}
- (void)receiveNotification:(NSNotification *)notification {
  [self.bridge.eventDispatcher sendAppEventWithName:TestEventName body:@{@"name": @"Jack"}];
}
@end

在JS中,我們這樣接收事件

NativeAppEventEmitter.addListener(BGNativeModuleExample.TestEventName, info => {
      console.log(info);
    });

注意: 編寫OC代碼時,需要添加@synthesize bridge = _bridge;,否則接收事件的時候就會報Exception -[BGNativeModuleExample brige]; unrecognized selector sent to instance的錯誤。

上面原生代碼就編寫好了,主要以代碼實踐為主,彌補官方文檔中的一些不足,如果要需要了解更多的原生模塊封裝的知識,可以參考原生模塊,也可以參考官方的源代碼。

二、發布上線

我們按照上面步驟編寫好原生模塊之後,接下來將我們寫的原生模塊發布到npm。

1、我們需要創建github倉庫

在github上創建一個倉庫react-native-BGNativeModuleExample,然後關聯到我們前面創建的react-native-BGNativeModuleExample目錄

$ cd TestProject/node_modules/react-native-BGNativeModuleExample
$ git init .
$ git remote add origin https://github.com/liuchungui/react-native-BGNativeModuleExample.git

2、我們需要創建原生模塊的入口文件

我們需要在react-native-BGNativeModuleExample目錄下創建一個index.js,它是整個原生模塊的入口,我們這裡只是將原生進行導出。

//index.js
import React, { NativeModules } from 'react-native';
module.exports = NativeModules.BGNativeModuleExample;

3、發布到npm

在發布到npm之前,我們需要創建一個package.json文件,這個文件包含了module的所有信息,比如名稱、版本、描述、依賴、作者、license等。 我們在react-native-BGNativeModuleExample根目錄下使用npm init命令來創建package.json,系統會提示我們輸入所需的信息,不想輸入的直接按下Enter跳過。

$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install  --save` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
name: (react-native-BGNativeModuleExample)

輸入完成之後,系統會要我們確認文件的內容是否有誤,如果沒有問題直接輸入yes,那麼package.json就創建好了。 我這裡創建的package.json文件如下:

{
  "name": "react-native-nativemodule-example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/liuchungui/react-native-BGNativeModuleExample.git"
  },
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/liuchungui/react-native-BGNativeModuleExample/issues"
  },
  "homepage": "https://github.com/liuchungui/react-native-BGNativeModuleExample#readme"
}

如果我們編寫的原生模塊依賴於其他的原生模塊,我們需要在package.json添加依賴關系,我們這裡由於沒有相關依賴,所以不需要添加:

"dependencies": {
}

初始化完package.json,我們就可以發布到npm上面了。

如果沒有npm的賬號,我們需要注冊一個賬號,這個賬號會被添加到npm本地的配置中,用來發布module用。

$ npm adduser
Username: your name
Password: your password
Email: [email protected]

成功之後,npm會把認證信息存儲在~/.npmrc中,並且可以通過以下命令查看npm當前使用的用戶:

$ npm whoami

以上完成之後,我們就可以進行發布了。

$npm publish
+ [email protected]

到這裡,我們已經成功把module發布到了npmjs.org。當然,我們也別忘記將我們的代碼發布到github。

$ git pull origin master
$ git add .
$ git commit -m 'add Project'
$ git push origin master

有時候,有些文件沒必要發布,例如Example文件,我們就可以通過.npmignore忽略它。例如我這裡.npmignore文件內容如下:

Example/
.git
.gitignore
.idea

這樣的話,我們npm進行發布的時候,就不會將Example發布到npm上了。

4、添加Example,測試是否可用,添加README

我們在react-native-BGNativeModuleExample目錄下創建一個Example的ReactNative工程,並且通過rnpm install react-native-nativemodule-example命令安裝我們發布的react-native-nativemodule-example模塊。

$ rnpm install react-native-nativemodule-example
[email protected] /Users/user/github/TestProject
└── [email protected]
rnpm-link info Linking react-native-nativemodule-example ios dependency
rnpm-link info iOS module react-native-nativemodule-example has been successfully linked
rnpm-link info Module react-native-nativemodule-example has been successfully installed & linked

上面提示安裝並且link成功,我們就可以在js中進行使用了。

import BGNativeModuleExample from 'react-native-nativemodule-example';
BGNativeModuleExample.testPrint("Jack", {
    height: '1.78m',
    weight: '7kg'
});

5、我們在發布上線之後還需要編寫README文件

README文件是非常重要的,如果沒有README文件,別人看到我們的原生組件,根本就不知道我們這個組件是用來干啥的。所以,我們很有必要添加一個README文件,這個文件需要告訴別人我們這個原生組件是干什麼的、如何安裝、API、使用手冊等等。

6、原生模塊升級,發布新版本

當我們添加新代碼或者修復bug後,需要發布新的版本,我們只需要修改package.json文件中的version的值就行了,然後使用npm publish進行發布。

總結

本篇文章主要分成兩個部分,一是講述了編寫原生模塊的知識,二是將我們編寫的內容發布到npm上。

參考

如何發布Node模塊到NPM社區

原生模塊

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved