为React Native编写原生模块 #126
zhangyu1818
announced in
zh-cn
Replies: 1 comment
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
国内开发React Native的基本都是Web前端,一般都没有原生开发相关的知识,所以有一些与原生相关的功能就难以实现。
近期因为要封装一个SDK给React Native使用,所以仔细研究了一番,实际上原生模块的编写并不复杂。
本文简单的记录一下原生模块的编写过程,发布至npm并在主项目中使用,需要熟悉React Native及其目录结构。
项目创建
原生模块的项目创建比较麻烦,我没有深入研究如何搭建项目,而是使用一个很好的脚手架项目,能够直接为我们创建一个模版项目。
本文名称就使用默认的
react-native-awesome-module
。输入对应的信息
这里语言选择为 Kotlin & Swift,因为这2种语言对TypeScript开发者更加友好。
下一步类型选择 Native module (to expose native APIs)。
接着根据提示执行
yarn
安装依赖。目录结构
只需要关注以下目录
src ——
npm publish
后别的项目会引用这里的文件,这里会做一些原生方法导出,类型定义。ios —— React Native iOS端会加载的文件,这部分我们会使用Swift编写
android —— React Native Android端会加载的文件,这部分我们会使用Kotlin编写
example —— 运行example项目来调试我们的原生模块
语法简介
简单以TypeScript为例子介绍下语法
let
var
var
const
let
val
function
func
fun
object
[String: Any]()
Map
const array:string[] = []
let array = [String]()
val a: Array<String> = []
iOS端
iOS端的
.podspec
文件在项目根目录,我们需要在这里将我们模块的依赖写上。s.platforms
表示此模块最低的iOS版本,如果主项目的版本低于此文件的版本,那就无法使用此模块。s.dependency
表示此模块依赖的Pod
包,如果我们要依赖额外的Pod
包,则需要添加。比如我要将
GooglePlaces
作为依赖,那我需要添加以下内容。因为
GooglePlaces
在iOS 10最高只能使用4.2.0的版本,所以我需要标记版本。接下来我们看iOS模块的文件。
编写iOS模块
在此之前建议先阅读官方iOS模块文档。
双击
ios/AwesomeModule.xcodeproj
使用Xcode打开项目。接下来看目录。
AwesomeModule-Bridging-Header
—— 此文件为Objective-C和Swift的bridge文件,因为我们是用Swift来编写所以需要此文件,通常我们不需要改这个文件。AwesomeModule.m
—— 我们需要将要导出的模块和方法声明在此文件。AwesomeModule.swift
—— 我们需要将方法的实现写在此文件。AwesomeModule.m
此文件里的语法还是Objective-C语法,一看就会令人难以理解。
RCT_EXTERN_MODULE
为OC里面的宏用来导出我们的模块和方法。这里参数里的
AwesomeModule
为我们的模块名称。这里就是导出了一个计算乘积结果的原生方法,方法名为
multiply
。(float)a
代表第一个无名参数,类型为float
withB:(float)b
代表第二个名称为withB
的参数,变量名为b
,类型为float
。withResolver:(RCTPromiseResolveBlock)resolve
代表JS中Promise的resolve
回调。withRejecter:(RCTPromiseRejectBlock)reject)
代表JS中Promise的reject
回调。因为Objective-C中传入参数是有名字的,所以会看上去怪怪的,伪代码举个例子。
AwesomeModule.swift
这里Swift的代码,至少是没学过也可读的。
类
AwesomeModule
继承NSObject
,里面有一个方法multiply
,没有返回值,它有4个参数,由于要传递给OC调用,所以需要添加@objc
。添加新方法
比方说我们要添加一个新方法,在JS端应该这样被调用。
添加定义
实现方法
这只是一个简单的例子,实际场景肯定是调用别的包的方法。
在这个方法里,如果我们传入了
filter
,则会调用resolve返回query + filter
,否则reject抛出错误。此方法使用
DispatchQueue.main.async
在iOS的主线程开启了一个任务,里面调用了resolve
。我们称为回调的在iOS里称为闭包,
@escaping
表示resolve是一个逃逸闭包。其实这里比较好理解,在JS里,如果A函数里返回了B函数,B函数使用了A函数里变量,自动就闭包里,iOS需要使用
@escaping
来保持对此变量的持有。需要实现的额外方法
在iOS中,它的布局UIKit是运行在主线程的,而我们的React Native是运行在别的线程的,在别的线程里是不能操作主线程的UIKit的,所以这个时候通常需要调用
DispatchQueue.main.sync
或者DispatchQueue.main.async
来执行我们的操作。所以我们需要需要实现额外方法,来告诉React Native此模块应该运行在那个线程。
如果我们的模块不需要在主线程初始化,我们需要将
requiresMainQueueSetup
的值返回false
,也不需要methodQueue
这个属性了。Android端
Android端相比iOS端会简单很多很多,没有OC上古语法,没有声明文件,直接写就行了。
Android端如果我们的模块有额外依赖,写在
android/build.gradle
里就行了。还是以添加
GooglePlaces
为例。同一个SDK,Android端和iOS端可能包名版本都不一样哦~
编写Android模块
我们只需要关注
AwesomeModuleModule.kt
这个文件就行了。AwesomeModuleModule.kt
只需要在需要导出的方法上加一个
@ReactMethod
注解就行了。还是以上文需要新增的JS方法为例。
是不是很简单!
值的互相转换
iOS,Android和React Native的转换需要我们自己做一些操作。
React Native传值给原生
有以下结构的值
iOS
Android
原生传值给React Native
一个简单的例子
有以下结构的原生对象(这里仅仅以TypeScript做为类型定义)
以下仅供参考,实际情况不单单只是转成
Dictionary
或者Map
就行。iOS
Android
Android这边需要使用React Native提供的
Arguments
,WritableMap
之类的。转数组用
Arguments.makeNativeArray
,转Map
用Arguments.makeNativeMap
总结
总体还是比较简单,因为我也是从零开始花了几天时间做了一个,但是公司内不让开源,不能发出来给大家参考。
我仔细想了想,虽然我Swift一年时间里都断断续续的在学,但是开发模块其实并不怎么需要原生基础,基本就是调用原生SDK方法,然后暴露给React Native。
只需要看看Swift文档,Kotlin文档,简单了解下语法就行了。
如果需要写原生视图的包就比较复杂了,这就要求必须掌握一定的原生能力了,目前我也还没涉及。
春节放假第一天,写了2个小时,都没玩游戏。
春节了,终于可以好好休息了!祝大家春节快乐!
Beta Was this translation helpful? Give feedback.
All reactions