序言
这是一个在StuQ做分享时的讲稿。当然还有很大的改进空间,但我这会却没有什么劲头继续改进,既然这样就不如索性先发出来,先把它从脑袋清出来,把其它我更想做的事情做了,回头再看哪天想改了再回来改。
当然,大家有觉得不对的地方,随便批,我会当做问题之一,以后努力改进:)
分享环节:
大家好,我是深圳平安科技的利炳根。网名:清醒疯子。主要研究iOS和ReactNative。
今天和大家分享的是我在ReactNative上的一些开发实践经验。请大家多多指教。
在ReactNative上的研究,我还是做得比较粗浅,有不对的地方,大家随便批,我多向大家学习。
今天的内容分为四个部分。分别的ReactNative的开发环境搭建、JS/Bundle的管理、混编以及数据流的处理。
ReactNative的开发环境搭建比较简单。只需要把node、watchman、flow通过brew安装完,通过npm把ReactNative命令行安装到全局,就可以用init命令创建ReactNative项目。
其中,node是node.js。它有非常好用的包管理工具npm。也可以拿来写后台。我自己经常拿来在本地快速写一些简单的后台辅助调试代码。
watchman是Facebook的开源项目,主要用来监视文件,记录文件的改动。React通过watchman来实现代码发生变化时完成相关的重建功能。
flow也是Facebook的开源项目,用来做JavaScript的静态类型检查。用来发现JS程序里的类型错误,提高编码效率和代码质量。包括前期错误检测、代码智能提示等。
安装ReactNative命令行的时候要注意加上-g这个参数,把RN命令行安装到全局,才方便我们在任何目录通过命令行创建ReactNative项目。
ReactNative项目创建的时候,会自动把一些需要用的库加到项目工程里,自动做好基本的依赖管理,并且自动生成最简Demo代码。创建完之后就可以运行一个最简单的Demo,方便大家体验ReactNative的编程。
目前,ReactNative还没有特别好用的开发工具。开发效率会稍微打点折扣。但利用现有的开发工具还是足够完成基本的开发任务的。
Nuclide是Facebook的开源项目,是与Ract配套的IDE。目前还在不断地完善当中。已经能实现基本的代码提示和静态类型检查。Nuclide目前还不是独立的IDE,需要作为Atom的包进行安装。
Atom和WebStorm都可以做到React的部分代码提示。对ReactNative的支持就相对差一些。主要还是针对JavaScript的支持而已。
ReactNative的JSX只是JavaScript的封装。一些在JavaScript上的打包工具,ReactNative也能用。比如Browserify和Webpack。
Browserify使用比较简单。指明入口点和出口点就可以打包。而Webpack则一般要写一个配置文件webpack.config.js指明打包细节。当然,Webpack的功能相对就更强大一些。
Nuclide的开源项目代码提交还是比较活跃的,对这方面有兴趣的朋友不妨关注一下,参与其中和大神一起做一个IDE也不失为一种相当不错的娱乐项目。
虽然ReactNative最终会渲染成Native的View,但它只能通过浏览器调试。基本上就是用Chrome来做。
Chrome上有一个插件React Developer Tools,不过要翻墙才能装。
通过Chrome的consle可以打印变量。如果对这块不熟悉的话,可以先通过调用一个网络请求,打印返回的数据,并根据数据调整显示,来熟悉一下Chrome的调试过程。
在JavaScript代码中插入debugger;就可以作为调试过程中的断点。
在iOS平台上,ReactNative组件通过RCTRootView这个类接入到Native的视图层次里头。
RCTRootView初始化只需要提供两个参数,一个是JS包的路径,一个是模块的名称。
ReactNative的应用组件,最后渲染出来都是一个View的子类,都可以通过addSubView加到Native的视图层次上面。
ReactNative可以实现动态编码。只需要在远端修改JS文件并保存,Native端无需重新编译,只需要Reload刷新一下,就可以更新APP视图和功能。
在设置RCTRootView的JS包地址时,设置为远端的JS文件资源网址,在引用时把文件后缀改为.bundle,即可以调用远端的ReactNative bundle命令,把对应的JS文件打包为可用的JS包文件,在Native端实现视图的渲染。
bundle命令有两个常用的参数。platform参数指定打包到iOS平台还是Android平台。dev参数指定是否开启开发选项以提供更多的调试信息。
调用远端JS文件打包的方式有一个缺点,打包过程导致视图初次加载比较慢,影响用户体验。可以过加载离线包的方式解决这个问题。
在设置RCTRootView的JS包地址时,设置为Native端沙盒内的jsbundle文件地址,ReactNative就可以从Native端加载渲染视图,速度有明显提升。
通过bundle命令,可以把JS文件打包成jsbundle包。bundle命令有三个常用参数。platform参数指定平台,entry-file参数指定入口文件,bundle-output参数指定出口文件。
通过加载Native端离线包jsbundle文件,在提升加载速度的同时也让ReactNative失去了远端更新的能力。这样得不偿失。我们可以通过静默下载jsbundle文件替换Native端jsbundle文件的方式解决这个问题。
通过远端jsbundle文件资源网址,启动一个下载文件请求。在收到请求返回的时候设定文件路径。在收到返回数据时写文件。
写文件时,要注意先让写入点移动到文件末尾,再进行文件写入,避免覆盖之前接收到的数据。
ReactNative因为技术发展不久,还有部分Native端的功能并没有完全实现。在项目开发过程中有可能需要在ReactNative项目里加入Native端的功能代码辅助。
ReactNative有一套机制实现。在Native端的类里引用RCTBridgeModule,并遵循RCTBridgeModule协议。通过宏RCT_EXPORT_MODULE向JS暴露Native端的类,通过宏RCT_EXPORT_METHOD暴露Native端的方法,通过RCTResponseSenderBlock实现JS端的回调。
JS端的处理和Native端同样简单。在JS文件的开始引用NativeModules组件。通过NativeModules引用Native端指定类。由Native端指定的类就可以直接调用指定的方法并指定回调的实现。
Native项目添加ReactNative模块稍微复杂一点。首先需要进行一些工程配置。把ReactNative需要的静态库加到工程里。在Build Settings里设置Other Linker Flags 和Header Search Paths。最后还要在Info.plist里设置App Transport Security Settings -> Allow Arbitrary Loads。
在Native的类中引用RCTBridge和RCTEventDispatcher。通过bridge的eventDispatcher向JS端派发事件。派发事件的接口有5个,分别对应5个不同的事件类型。
这里要注意,不要在Native端的viewDidLoad方法里向JS发送事件消息,因为这个时候ReactNative组件还没有挂载出来,接收不到消息。
JS端在文件一开始引用NativeAppEventEmitter组件。通过NativeAppEventEmitter组件的addListener方法监听Native端派发的事件消息。组件卸载时,通过NativeAppEventEmitter组件的removeAllListeners方法移除监听。
ReactNative的数据流处理,目前业内有4种方案。原生的ReactJS通过Props - States - Components的机制处理数据流。
缺点很多。扩展性差,增加新功能时,需要在每一个用到的组件里复制一遍代码。
可读性、可维护性差,代码量大,难以调试、测试。
可移值性差,代码耦合度高,视图、数据不分层。
违背单一数据层原则,在视图层直接进行数据操作。
Facebook官方推荐的Flux,通过Actions - Dispatcher-Stores - Component的机制处理数据流。
代码冗长,Dispatcher和Store有很多重复烦琐的人工检查、定义。
多个Store中处理同一个Action,将产生大量的waitFor方法处理Store的先后逻辑。
多个Store间共享一个State,State有可能被分散到多个Store中,State的变化将变得不可控。
多个Store互相依赖,还有可能产生依赖循环。
Redux通过Actions -Store - Components的机制处理数据流。
Redux针对Flux做了不少优化。通过内部拓展actions的行为,移除了单例的dispatcher。stores可以监听actions的行为,无需进行冗杂的switch判断。
stores可以相互监听,可以进行进一步的数据聚合操作。waitFor方法被连续和平行的数据流所替代。
Redux也有自身的一些弊端。由于整个应用的数据都集中在1个Store里,数据和处理逻辑的复杂度将不可避免地变高。
因为语法相似,从ReactJS、Flux迁移到Redux成本较低,建议旧项目迁移采用Redux。
Baobab通过Actions -Baobab Tree - Components的机制处理数据流。
Baobab的优点十分明显。应用所有状态都存在一棵数据树里。可以非常简单地实现数据域的切换。应用状态树的存在使得数据流管理变得非常简单,代码量少。
通过Baobab库创建状态树,数据以键值对的方式存储。
Baobab通过select方法直接切换数据节点,还可以通过cursors创建数据游标组,访问数据。
Baobab通过set方法修改数据。
如果项目新建,没有旧代码维护的包袱,个人建议采用Baobab来处理数据流,一定会更省事省力。
结束语:
我的分享到此结束,非常感谢大家今天来参加,希望上面分享的内容能对大家有所帮助。如果大家对ReactNative或者其它问题想跟我交流,欢迎到我的支付宝经费群来。
问答环节:
1.能说说选择相较PhoneGap,ionic等些其他平台,为什么选择React么?
PhoneGap是比较早期的技术,一直以来,在性能上或者功能的覆盖上,大家都不够满意。尤其是性能。RN因为最终是渲染成Native的View ,体验上就好很多。同时呢,因为RN是Facebook在推,从Github上的Star,大家也可以看出业界还是对Facebook有很大的期待和信心的。
2.请问rn中的组件出现相互嵌套的问题可以怎么解决?即 a require b, b 也require a。当调用a时,就会b=null了。可以用es6的模块解决吗?
这就是涉及到数据流的处理。如果用原生的ReactJS去处理,是会非常麻烦的。Facebook希望Flux能解决这个问题,但实际下来,大家觉得满足不了需求。然后业界就有了Redux和Baobab两种方案。Baobab是最新的方案,解决方式也最优雅,大家有兴趣可以重点研究一下。
3.既然是在Mac下,为什么不用safari调试,当前safari调试窗口也是很棒的
在我自己这边主要是习惯问题。一直以来用的都是Chrome。因为很早之前调试API啊各种东西都是用Chrome搞。既然Chrome能搞定就懒得换了。
4.Native与js调用时,与hybrid方式的app有什么不同。
RN的双向通信还是做得比较好的,上面的分享也有讲到,实现起来非常简单。我在这上面多花了一点时间,也只是因为把发消息的代码放到了AppDelegate上,结果死活收不到。Hybrid,常见也是用Bridge。这块的麻烦是,需要HTML5的同事配合。
5.再请教一个问题。组建多层嵌套时,组建间该如何通信?特别是最底层跟顶层的通信,除了props之外,还有啥好办法吗
这个其实就是第四部分数据流的处理问题。Baobab可以把数据统一到一个地方,而且数据域切换非常方便,可以看看。
6.提问:React Native对安卓的支持情况,是否能兼容如此碎片化的安卓
目前,我们还没有去处理具体的适配。这块因为主要是Android的同事在研究,我也不大清楚。不过,既然很多大厂都有用RN,应该不会有太大问题。
8.没有ios经验,只有reactjs经验的,怎么上手xcode下react native开发配置呢,或者android下的
Native下的工作量是非常少的。基本上只要随便找几篇入门的文章看看,问题就不大了。遇到搞不定的问题多上stackoverflow搜搜。或者找我交流也可以:)
9.rn在开发团队中的分工如何,如界面,js,ios程序员如何合作
目前我们是1个iOS和1个Android,然后就是后端了。其实重点的开发量反而是JS上的。一旦把平台需要配合的东西摸清,剩下的主要工作量就是写JS了。两个平台的多数组件都是可以共用的,也就是很多代码只要写一份就可以了。
11.rn适合渲染view还是处理各种逻辑呢,有不如原生的地方吗
目前为止,我还是学习RN就算是View上也做得没有Native漂亮。如果逻辑比较复杂,是不是适合全部用RN来做,还有待考察。RN目前主要的针对点还是迭代比较快的相对轻一点的业务。反正熟悉之后,混编是非常简单的事情,大不了拿Native做一个复杂业务的API:)
问题回答完毕:)