背景
公司要开发一款新的应用,一方面希望能像H5一样跨平台和动态更新,另一方面又不满原生嵌入H5这种性能瓶颈,于是我便开始进行一系列调研,包括RN、Flutter、Uni-app等,本篇文章主要给大家分享RN调研的一些结果。主要内容有:
1、RN 是什么?
2、RN 为什么出现,它带来了哪些好处?
3、RN如何实现跨平台?
4、RN热更新机制。
5、RN是如何解决H5性能瓶颈的?
6、RN快速上手步骤。
7、RN有哪些缺点,未来发展方向。
RN是什么?
RN是React Native的简称。其中React(有时叫React.js或ReactJS)是一套开源JavaScript 库(也可以称为前端 UI 框架),而React Native则是React向移动端的延伸。我们可以认为React Native分为两层,React 层是一套JavaScript 库;Native层则扮演桥梁的功能,根据平台不同映射为不同的原生控件;通过这种设计实现了—开发者构建的RN代码可以在不同的平台上(暂时支持Android和IOS)运行,也就是Learn once, write anywhere。
问题A:为什么不是Write once, run anywhere呢?
RN 为什么出现,它带来了哪些好处?
React 最初来自 Facebook 内部的广告系统项目,随着页面与逻辑越来越复杂项目的前端开发遇到了巨大的挑战:当更新一个页面元素,需要了解整个View DOM树和所有关联节点的业务等才能正确更新UI与业务逻辑,当页面复杂时候,这是一个很考验耐心的工作,很容易遗漏,所以他们有一种技巧,修改部分UI与逻辑,但是全部重新绘制渲染,这也是react的初步想法之一。
简单、直观、可靠是软件攻城狮们孜孜不倦的追求!!
为了达到上述目的,Facebook攻城狮对市场上所有 JavaScript MVC 框架进行审核,MVC需要通过C来绑定View监听数据变化,来实现级联(相关View的)更新。 但是在小框架上,V和 C往往很难分离,分离反而不利于阅读和理解代码。因此Facebook对主流的JS框架很不满,任性的他们就决定自己写一套,用来架设 Instagram的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了,这就是React。React设计目的就是解决目前JS框架不够直观和业务逻辑复杂的弊病,所以React 最有价值的是声明式的、直观的编程方式,这也是它这样命名的原因(React: 响应,一修改就可以很直观的看到效果)。
RN如何实现跨平台?
在围绕原生与H5交互实践聊聊Android混合开发,我们有谈到H5能够实现跨平台的原因在于Android和IOS都实现了一个内置浏览器-WebView,分别通过Android和IOS上的这两个浏览器来解释运行H5。
同理RN也是分别利用了Android和IOS的两个翻译器Jsc.so和JavaScriptCore,通过这两个翻译器RN代码就可以翻译成为Android或者IOS代码,从而在android或IOS系统上运行。具体可以参考文章:React Native运行原理解析。
总揽所有的跨平台技术,其实和指挥多国联合部队差不多,指挥官发出命令,每个国家的部队接收这个命令时,必须有对应的翻译系统将它翻译成本国语言,才能使之得到执行。所以每个翻译系统精准度、稳定度、效率都是衡量整个(多国作战系统)跨平台技术优异的指标。至于在每个国家如何构建精准、稳定、高效的解释翻译系统,则需要根据指挥官语言和这个国家语言进行针对性设计。我接下来会有文章对翻译解释系统进行讨论,欢迎大家在这里留言,提供想法。
RN热更新机制
谈到RN不得不谈一谈它的热更新机制,在围绕原生与H5交互实践聊聊Android混合开发,有提到H5与生俱来的结构设计,是把数据和数据展示方式都当做数据从服务器下发,也就是说H5页面可以包含所有的业务逻辑和数据信息,它天生具有热更新机制。
RN热更新的原理就是从服务器拉取新的JS bundle文件,并重新加载。具体可以看看通过对RN热更新的剖析来感受热更新思维一文。JS bundle,bundle: 捆,JS bundle一捆JS,可以认为是H5页面集合,对比H5一页页的拉取,RN是一次性拉取下来,并给这个集合一个版本号,如果版本更新再去拉取新的JS页面集合,一次性拉取下来之后,js就是本地文件,利用RN解释翻译器(JavaScriptCore)去解释执行RN代码(JS 集合中的JSX代码)。所以RN热更新的核心技术还是构建RN的解释器翻译器。
谈到这里,我有一个问题,问题B: 热更新思想的实质是什么?它的发展方向是什么?
个人见解:
热更新思想: 一切都是数据流( every thing is data flow)
热更新核心技术: 构建跨平台解释(翻译)系统
大家有什么想法,请在下方评论。
RN是如何解决H5性能瓶颈的?
首先我们需要了解H5性能瓶颈是怎么造成的?对比原生,H5包含信息更加自由丰富,这也导致:
1、从服务器获取信息时候,H5页面需要更多的数据量和校验步骤,相应的需要消耗更多的时间;
2、本地解析渲染(显示)时候,H5需要更复杂的解析渲染规则,相应的需要耗费更多的时间。
RN可以认为是H5(JS)的特殊子集合。那么RN解决H5性能瓶颈,自然也是从这两点进行出发的。
首先我们看问题1,它关键在于正常使用时候减少网络请求的数据量。这一点我们其实已经从一个名词( JS bundle)中猜测到,RN相当于进入一个模块时候(可以自定义分包)把需要更新的的H5页面打包成一捆(js bundle,可能是一页,也可能是好几页)一次性更新,当再次加载该页面的时候,就直接从本地获取就行了。在这种方式下,当非首次加载JS bundles时候,从网络请求的数据跟原生基本差不多,甚至一些特定情况比原生还要少。所以问题1的答案就是是运用(批量)缓存的方式来减少平时使用过程中网络请求数据量。
接下来我们再研究问题2,它关键在于缩短JS代码解析渲染的时间,为解决这个问题,Facebook采用了两个重要举措:
1)引入虚拟DOM减少不必要的渲染;
虚拟DOM、虚拟硬盘、虚拟机等计算机术语中提到的“虚拟”基本都是为了解决“真实”操作起来复杂、繁琐、代价高等问题而生,它们都能够将相对简单方便的虚拟操作映射成为真实的行动。虚拟DOM的出现之前,一次页面更新可能有十几个DOM更新操作,前前后后更新了十几次,大大浪费了渲染性能。出现虚拟DOM之后,一次更新操作中的10次更新DOM的动作,不会立即操作DOM,而是将这10次更新更新到虚拟DOM中,通过对比找出前后两次虚拟DOM的不同(diff算法),然后把不同一次性映射为真实DOM的更新。
2)舍弃JS渲染,通过映射的方式把RN代码中的控件一个个映射为原生控件进行渲染。
由于JS的灵活性与复杂性,虽然很多团队在研究它的渲染问题,但它的解析+渲染始终比原生慢上许多。于是facebook工程师另辟蹊径,它认为既然原生渲染快,那就用原生渲染代替JS渲染,RN做好桥接就好了。比如我们用RN代码写一个Button,那么实际在运行的时候RN会生成一个映射表,将这个Button分别映射为android和ios的button,相关设置的属性也进行了同步映射。
从上面可以看到,正是出于对H5的性能优化和向移动端的延伸思维产生了RN,那我这里留下几个问题给读者:
问题C:RN能否做到android端和IOS端统一,如果不能为什么,如果可以怎么做 ?
问题D:RN代码在本地运行时是通过javaScriptCore边解释边运行,还是提前翻译成原生代码,甚至是机器码放在本地运行,facebook为什么这样设计?
RN快速上手步骤(已有实践的同学可略过)
一 、RN开发环境搭建
请参考RN中文网-环境搭建
二、RN本地运行与调试
1 、运行请参考RN中文网运行篇
这里我仅仅解释下命令react-native run-android和(IOS 本地运行需要)npm start
这两个命令都会在电脑上起一个服务进程Watchman,Watchman是由Facebook提供的监视文件系统变更的工具,这个服务进程起来后就会监听RN文件目录下文件是否变化,如果变化就把相关内容打包发送给手机,从而实现了本地热更新。(你的电脑相当于远程的服务器,手机从你电脑上获取新的JS bundle)。
2、关于打开本地热更新,摇一摇手机打开本地热更新功能选择界面,或者在命令提示符窗口输入:adb shell input keyevent 82。
3、 关于调试请参考RN中文网-调试
三、RN开发基本流程
React 是一个全新思路的前端 UI 框架,它完全接管了 UI 开发中最为复杂的局部更新部分,擅长在在复杂场景下保证高性能;同时,它引入了基于组件的开发思想,从另一个角度来重新审视 UI 的构成。通过这种方法,不仅能够提高开发效率,而且可以让代码更容易理解,维护和测试,所以RN开发的一些流程和原则与原生有很大不同。
1、绘制原型图,如果有直接用;
2、列出所有需要使用的基础组,一个基础组件只负责一个功能;
3、对基础组件进行分层;
4、搭建静态页面;
5、分析页面之间的关系;
6、确定数据显示者和拥有者 ;
7 、确定状态机变量最小集;
8 、确定各个事件的接收者和处理者;
9 、实现界面业务逻辑。
三、RN开发原则与规范
1、不能用“this.state.某状态机变量名 = 值”来刷新页面。this.state.值是可以获取到具体state的值,但是它不会引发render,所以这样赋值是不会生效,牢牢记住state值是跟页面UI变化绑定的一起的。应该使用this.setState进行赋值, 因为setState异步的,而且会进行state状态合并,所以最好的是,时间顺序无关的state可以如下赋值:
this.setState({
bannerImageUrl:jsonContent.androidImgUrl,
}
如果一个state值连续改变,请传递一个方法到setState中确保setState能够正确执行,如下:
this.setState((state) => {
return {
number: number+1,
}
})
this.setState((state) => {
return {
number: number+2,
}
})
2、如果跟ui无关的变量尽量不要定义在state中 ,会导致页面绘制。
3、组件成员变量定义请用如下方式:this.myProperty='HaHa'
4、尽量让自定义的组件成为无状态的React native 组件,无状态意思就是没有状态机 state。
5、尽量创建多个无状态(只负责渲染数据)的React native 组件,将他们封装在 一个有状态的组件中,并把有状态值通过props 传给无状态的 react-native 组件。
6、让UI中可变数据来源是状态机变量和属性。
7、 遵循自底而上的设计开发原则。
RN有哪些缺点,未来发展方向
从上面可以看出RN最大优势是开发者友好(声明式的,直观的编程方式),它基本原理在于提供一套从JS到原生的映射,从而实现JS代码拥有接近原生的运行效果。
但是这种另辟蹊径的方式下的缺点也是有不少的,最明显的就是两端的不统一,JS在Android平台和IOS平台都映射为Android和IOS的原生View进行渲染,Android和IOS原生不一致的话,那么自然会出现双端不一致;其次这种多端桥接的方式,等于说RN兼容JS版本和原生版本,JS版本和原生版本不断进化千万万,做RN的同学很能体会到这种兼容性的坑;最后就是做RN需要会Android、IOS、JS这一整套全懂,学习成本高。
随着5G的到来,端到端的传输速度大大增加加之新一代Web渲染引擎的研发,RN对WebView的性能优势逐渐失去;而RN多端不统一的问题越发尖锐,比如谷歌大佬推出的Flutter,用了一套全新的渲染机制,真正实现了一次书写,多端运行,但是由于多种原因比如dart语言的普及率、Flutter热更被禁止、Flutter社区不够成熟等,Flutter还未真正普及起来,但未来可期,强烈冲击着RN的地位。
所以RN迫切需要变革,一方面强化自己的优势(入手快,直观、用户友好)保证React框架在前端的霸主地位,另一方面向Flutter学习做好多端统一。但不管RN的将来能重新繁荣昌盛还是成为历史云烟,RN值得每一个做技术的学习,它的简单、直观,它的桥接转换另辟蹊径。