本文目录:
一、React Native 已经成为了移动前端技术的趋势
二、基于React Native 进行移动平台研发过程中的一些思考
三、基于React Native 进行移动平台研发过程中的一些实践
四、小结
一、React Native 已经成为了
移动前端技术的趋势
从2014年年底,Facebook计划开源React Native 的时候,我就已经开始关注TA了,关注的主要原因是,我们在2012年的时候,将我们的移动平台前端开发技术确定为“DSL->Javascript->Native Mobile”这个技术流派。要知道在那个时代,绝大多数的友商要么选择Hybrid,要么选择HTML5作为移动平台的跨平台前端解决方案。
然而,这两种方案最终的UI渲染,本质上都需要依赖Webkit,通俗点说就是UI最终是通过浏览器内核渲染。我们当时在技术选型的时候实在无法容忍Webkit在Andriod上的体验,而选择了驱动原生(注:这个名字是我起的,也是为了区别于传统的Hybrid技术)的方式。
当时的这个技术抉择,在当时是冒着巨大的风险的,现在看来,我们是非常幸运的。
后来Facebook 推出React Native 后,阿里系也推出了自己的Weex,甚至Gartner针对这类技术在2016年的报告(IT Market Clock for Mobile App Development, 2016 )中首次出现并并为这个技术流派起了一个名字——Javascript Frameworks for Native Mobile。
Garnter将这个技术流派当如了“Advantag”中,可见Gartner对这个技术流派的认可。
Javascript Frameworks for Native Mobile这类技术的几个特点:
-
开发期基本采用类Web语言,比如React的语法。
-
运行期并不是采用Webkit做渲染,而是采用Native的渲染方式。
-
与Native 进行交互的通道是采用Javascript的方式。
当然,因其技术的先进性让各大互联网公司纷纷进行实践上的尝试,取得了不错的效果,包括天猫、腾讯QQ、手机百度、美团点评、携程等等。React Native 也建立了很好的生态,大家对案例如果有兴趣可以关注一下https://facebook.github.io/react-native/showcase.html
二、基于React Native 进行移动平台
研发过程中的一些思考
尽管React Native 在移动前端存在着无可比拟的优势,但每一家在工程化的过程中还是存在各自不同的思考。而作为移动平台,不是简单的解决单一的一个App的问题。
移动平台是支撑企业全面移动信息化的平台,需要解决企业面向不同场景下的各种诉求。针对移动App的使用者的场景不同,存在面向人和面向组织两种不尽相对的要求:
面向人:每个人对应的App功能是基本相同的。人与人是平等的,就如今天在线的各位和我一样,在支付宝里看到的功能是相同的。这种情况多出现在面向最终消费者的时候。
面向组织:是指功能因其所属的组织和职级决定了其所见和所能用的功能。当事人所处的组织机构发生了变化,功能也随之产生变化。
针对面向组织,需要举个例子来说明一下:假如我本人之前是一名普通的manager,其实对于产品线的经营报告并无权阅读(图一);当我被Promote 为产品线总经理的时候,我的App里就应该有“智能报表”的微应用(图二);当我调岗到别的团队(比如从事行政工作),我就不应该再具有“智能报表”的相关功能(图三),如下图所示:
随着岗位和职级的变化,功能从图一到图二再到图三,我还是我,而我App内的功能却发生了变化,这在企业中是非常常见的诉求。
实现上述的功能,从技术方案角度看,有多种方式,但是怎么更合理呢?我们可以思考一下。
首先,“智能报表”功能是否可以将UI已经打入App中,通过权限控制对应的前端“智能报表”是否显示?回答是不可以。主要原因有三:
-
类似这种功能在企业中非常多,如果要将UI全都打入到App中,这需要所有功能的全集,没有三四百M下不来。而用户实际上只有权限使用几分之一甚至是十分之一的功能,却要每次为此多更新两三百M,这是不合理的。
-
如果智能报表这个功能在用户安装ipa或者apk的时候,尚未开发完成,而是后续才迭代上线的,那么这个用户就无法及时使用到这个功能。
-
如果失去了相关的功能权限,需要的是相关的功能清除,甚至包括其相关的数据,而不是简单的隐藏,这既不安全又不合理。
这就意味着,移动平台必须能够动态的方式更新应用内功能,而且必须能够结合权限提供按需的热更新能力。
其次,在企业中不得不面对的是多供应商的问题,智能报表功能跟其他功能(比如:审批)是一个开发团队开发的吗?
显然,在企业中完全有可能是不同的供应商进行的开发。不同供应商之间,不
可能做到代码级的共享的,拿到所有移动项目的代码再进行打包,这是一件非常难以推动的事情。
移动平台必须保证对于多团队、跨地域的方式也能支持并行研发。这就意味着必须提供开发期的隔离。
移动平台需要支撑上述的业务场景,显然直接使用React Native 是难以满足要求的,这就引发了我们对于React Native实践的一些思考。
思考一:React Native 的学习成本和可替换性
作为移动平台,不得不考虑的是学习成本,在企业的供应商中是否能够对React Native的技术储备达到相关的要求,如何能够屏蔽对于技术实现的细节。
众所周知,React Native 发布版本非常的频繁,一个周之前已经发到0.44,对于大规模使用时,如何屏蔽版本的频繁升级导致的业务代码的重构,方便进行版本的可替换性。
如果能够将React Native实现换成其他实现(比如Weex),而上层业务代码能否不需要调整,真正做到实现的可替换性。
基于这一点的思考,移动平台采用了基于传统Web语法的DSL,作为开发期语言,降低了RN的学习成本;同时DSL层可以隔离业务代码与平台实现相关性,为后续RN版本更新等提供了良好的隔离,大致的示意图如下:
这里需要说明的一点是,我们并没有真正考虑基于Weex作为移动平台的一种实现,而是从技术架构上成为一种可能性。
思考二:React Native 的单bundle VS 多bundle
在谈论React Native的单Bundle与多Bundle的问题之前,首先,我们先回头看一下React Native 默认的Bundle 机制。
在基于RN编写App时,无论开发期创建多少个文件,RN都会将这些文件一并打到一个bundle里去,简单说默认的RN就是一种单bundle的方式,其打包bundle的大概过程如下:
因React Native 默认采用的是单Bundle的模式,所以,其更新机制也就仅仅能够以替换这个Bundle的方式进行,虽然有一些通过diff的方式提供增量更新的方式,但这种方案仍然无法满足上面例子中的“智能报表”的按需获取的能力。
另外,在进行编译打包的时候,需要获取所有项目的源代码,这对于多供应商的情况下也不适用。
所以需要解决的两个问题是:
1、在打包Bundle时,必须提供以多Bundle的方式进行。
2、在开发期,必须解决多微应用每个能够独立以Project的方式存在。
思考三:React Native 的调试的首屏进入VS 当前屏刷新
对于开发工程师,很重要的工作就是调试,以RN默认的单Bundle模式,势必会带来另外一个挑战,就是当资源发生任何变化时,必须重复上述的打包Bundle的过程并进行加载,看到的UI界面永远是第一屏。
实际上,我们期望的绝大多数场景是看到当前修改的资源所在的屏的UI效果。从这个维度看,我们必须能够将Bundle控制在一个资源的粒度,并确保当前bundle的动态热更。
另外,虽然React Native 默认不承诺跨平台,但跨平台(即一套代码同时支持iOS、Andriod)是移动平台的必备特性了。如何能够支持多屏同时调试,也将是一个必须考虑的问题。
思考四:React Native 的热更新VS 按需更新
说到热更新,这里不得不提的是几个月前,一堆的App被苹果拒掉的事情,这个事情曾一度让React Native 等Javascript Frameworks for Native Mobile 技术流派背黑锅。
其实这件本质上还是因为某些热更方案调用了私有的API而引起的,后来导致的局面时一堆三方的SDK都受到牵连,最终导致了使用这些SDK的App被拒。插一句,我个人觉着第三方的SDK在没有让使用它们的App知晓的情况下就进行热更新,就是耍流氓,谁又能保证更新后的SDK不做点什么呢。
回到热更本身,我认为,基于React Native 进行热更应该是一个必须的特性,而实际上我们需要提高要求,提供按需更新的能力。
三、基于React Native 进行移动平台
研发过程中的一些实践
基于上面的一些思考,我们基于React Native进行了一些实践,这里挑出几点给各位做个简单分享。
实践一:引入DSL层
首先,我们引入了DSL层,这里的语法采用了传统Web工程师熟知的HTML、CSS、Javascript,而使用移动平台的工程师无须对React进行过多的深入。在HTML的标签定义中,从语义上尽量能够对开发人员亲切,从习惯上尽量保留原有开发人员的一些习惯,比如对state的封装以getter、setter的方式提供能力,而这些标签需要一一以React Component的方式进行了实现。我们以Label为例(后续出现的代码均为示例代码片段):
DSL语言会在开发期编译成JSX,然后再编译成可被React Native 运行的javascript(涉及到拆分Bundle和编译,这里暂不展开)。
DSL编译成JSX,主要的工作原理大致如下:
-
HTML 标签的处理,主要是与RN的render进行关联
-
CSS 的处理,主要是与RN的StyleSheet进行映射
-
Javascript的处理,主要是嵌入到JSX中
上面的代码示例左侧为基于DSL语言编写的代码,右侧是生成JSX后的代码。
实际上,在工程化过程中,并不是像上面的示例代码那么容易做好,无论标签的定义,还是从DSL转换成JSX都是一个巨大的工程,且会遇到很多的问题。
实践二:拆分Bundle
在拆分Bundle上,我们遵守两个原则:
1、将系统库作为一个bundle文件,独立存在。
2、将每个的Module作为一个独立bundle文件
这种拆分原则将bundle拆分成小粒度的针对Module级别的bundle,这带来的好处是,可以方便的跟DSL中HTML文件进行一一映射,其加载单元的粒度可以理解为Page级别,而非整个App。
我们以require 为例,下图为默认的加载方式,如果没有对应的Module Factory,就会以异常结束,如下图:
扩展后,当判断没有对应的Module Factory的情况下,并不是以异常退出,而是增加了加载对应的Module级别的bundle,如下图所示;
当然,这就必须需要移动平台自行实现RTC_PM_JSCExcutor用于加载Module级别的Bundle。这一部分代码需要采用原生的Object-C或者Andriod Java实现,下面以iOS的示例代码
实践三:引入微应用
在将每个Module打成一个Bundle后,会让项目内资源的关系不易管理,这时我们引入了微应用的概念,用于完善应用内逻辑关系。
1、将原有的一个App对应一个Bundle的模式,改成一个App对应多个MicroApp,一个MicroApp对应多个Bundle模式。
2、将原有的一个Bundle对应多个Module的模式,裁剪成一个Bundle对应一个Module的模式
实践四:多屏调试
多屏调试与当前屏刷新,在移动平台IDE端的产品的定义中还是占有很重要的地位,因其直接影响了开发期的效率。
针对React Native 默认的编译核心框架,我们简单的可以总结为四件事情:
-
node-haste:主要是监听Module变化 ,把变化的Module从Module缓存中移除。
-
ModuleCache:Module编译缓存,把编译好的Module缓存起来,Module没有发生变化的情况下,直接使用缓存组装成bundle
-
Resolver:实现全局系统级库,语法级兼容实现,包括:ES5,ES6实现 兼容实现的引入 。实现Module factory的包装
-
JSTransformer:调用Babel编译JSX文件到JS。
其中1和2有很大原因是因为单bundle导致,当每个HTML文件对应一个Module,每个Module 对应一个bundle后,移动平台需要的就是监听HTML等文件的资源变化即可。如下图:
而这里的编译引擎基本上做的事情是:
1、DSL->JSX
2、JSX->js
其中后者主要的工作如下所示:
而为了能够更好的调试,需要对相关两种更新机制:
-
批量更新
a)包括初次批量更新部署,下载所有文件
b)使用过程中检查文件更新部署,判断需要更新的文件列表
-
单页更新
单页更新是确保其可以当前页保存,当前页刷新调试的主要机制
通过上述的方式,结合移动平台的IDE,可以提供
1、同时支持多手机终端的多屏调试(可以同时iOS和Andriod)
2、提供了当前屏动态刷新动态调试
实践五:按需热更
当上述的实践完成后,按需更新就成了一个相对较容易做到的事情。所以移动平台提供了两级打包编译机制,在无需调整代码的情况下,可以选择以微应用的方式出现其他的App内,还是以独立的ipa/apk的方式存在以移动设备中。其基本原理如下图所示:
四、小结
基于React Native进行移动平台研发是一个系统性的工程,上述的工作仅仅是其中的一小部分,期间的坑还有很多,这篇文章也仅是从大粒度的方面进行了分享。
希望本次分享对大家能有帮助,也希望各位专家能够多多拍砖。
拍砖方式如下: