React Web:让React Native代码跑在Web上
说明:公司项目尚未开源,本文仅阐述实现方案,不涉及具体技术实现细节,见谅。
===============正文分割线=============
前言
非死book发布React Native 已有两个多月,从开源初期我们就开始筹划的React Web终于也有了一个V1版本。在这次618大促的两个主会场中落地,实现了React Native代码到web的转换。
React Web的目的及意义非常明确: 让React Native代码跑在Web上让一套代码运行在各个移动终端,对前端及业务来说,这是开发效率中一个质的提升。在项目初期,我们也曾向 React团队咨询过类似的问题,他们团队的核心同学@vjeux也认为这是非常酷的事情,也是他们未来想做的事情。也许在发布React Native for Android的时候,也会发布React Web也说不定。(YY一下)
技术架构
基于React Native的适配方案,有几个:
-
制定一个Bridge标准,RN与RW 各自用最优的方式实现这套标准。比如基于Flex布局,我们实现一套一致的 Flex Component,
、 等。 | -
完全向RN看齐,RW实现RN的所有能实现的API。
在讨论中,最终选择了后者。
因为React Web的理念,让React Native代码跑在Web端,那么就决定了RW只是一个构建及打包工具,脱离RN,RW的实现则没有太大的意义,那么整体的技术方向就非常明确了: 实现RN一致的Style、Component及API,最终通过构建工具编译成web版本。
技术细节
Style补齐
RN中主要依靠FlexBox方式进行布局,它与CSS中的flexbox有一些差异,但概念基本是一致的。
在Style兼容方面,组内的同学做了很多探索,整理了布局中的差异性与共性。 在《react-native与react-web的融合》、《react-native之布局篇》两篇文章中有详细的论述。
举个2个例子:
Flex布局转换
在RN中,使用Flex布局,只需要简单使用flex: 1 即可完成Flex布局。而在Web端,则需要添加浏览器前缀,并给父级加上display:flex才能表现一致。
RN:
转换到Web
RW:
Border转换
RN :
转换到Web :
Style的兼容,主要是Flex布局的兼容,其他都是比较小的细节点,没有多大难度。
前期的原型demo
component补齐
component补齐是件非常有意思的事情,相当于有个架构师帮你把接口定义好了,你只需要实现对应的接口行为就行。
有2个组件,是整个RW转换最为关键,也是最有难度的组件:ScrollView、ListView 。刚好这方面,淘宝同学开发的xScroll与这两个native组件的理念非常接近。并且xscroll本身的实现也参考了Native中UIScrollView的接口。
也举个适配的例子:
Text组件中有个props numberOfLines 其表现的行为是多行文本截断。
在web端,可通过css来控制:
其最终比较效果:
View组件的效果,效果对比图:
API补齐
API的补齐,相对比较简单,没有太难实现的API,并且很多API均来自Web。比如:requestAnimationFrame、Promise。
其中比较特别的API:fetch。在Native中,是拉取json格式的数据。而在web端则会存在跨域的问题。在实现上,fetch不仅需要支持json,同时也要支持jsonp,对开发者透明。
对于系统级别的API,则返回空对象。
packager补齐
RN里实现了一套编辑及打包机制。这也是RW适配比较关键,同时也是技术难度比较大的一环。通过一致的构建,才能让代码无差异性。
在packager的实现上,@元彦同学在RN的基础上,实现了一套一致的打包工具,将RN的代码完美的编译成web版本。
举个例子:
在RN中,实现了一套require机制:可以不使用路径就可require文件。
比如:
var Dimensions = require('Dimensions');
学过nodejs的都知道,Dimensions 如果不带上路径,则查找当前目录和nodemodules包,而在RN中,Dimensions这个API并不在nodemodules里,而是在RN中的Libraries/Utilities/Dimensions.js中。
这种require方式,是在packager中通过DependencyResolver将所有文件读入内存,再进行包名查找替换。
实际应用
本次天猫618大促中的预热主会场及主会场,就是基于RN与RW的实现。无差异性运行在IOS平台上。下图是编译后的2个版本:
在实际的项目中,也暴露出一些问题:
-
性能问题。在android机器上跑RW版本比较吃力。
-
Flex布局本身(display: -webkit-box;)就是一个耗性能的css属性,大规模使用在低端android下性能堪忧。
-
每个component都有其生命周期,在Web端,每个View都是一个component,导致在dom结构上,比原生reactjs实现复杂一倍,而reactjs调用mountComponent方法会带来一定的性能损耗。也就是说,越复杂的页面,性能约差。
-
浏览器本身原因无法做到100%还原。比如RN中0.5px,0.3px的实现,在浏览器端除了iOS8外,其他web环境均不支持1px以下的数值。
-
抛弃了web的大部分功能,同时无法使用web最佳的方式来优化代码,而Native要照顾到web端的展示,也无法很好的使用一些特性,如上面说的0.5px实现。
-
无法解决所有兼容性问题。用纯web来写页面,也都会遇到浏览器间的兼容性问题,目前RW没有提供一套写Hack的机制来解决兼容性。感觉就像Chrome与IE6的对比,要完美兼容IE6,务必无法优雅的使用一些Chrome所支持的新特性。
还有一些其他的细节问题,后续会通过PPT的形式来分享给大家。
总结与思考
React Web的实现,其最大的难度在于性能优化。这也许是非死book不推崇write once, working anywhere的主要原因。
那么 RW的实现是否有意义,这取决于业务的侧重点,如果80%的流量来源于APP,那么在web端,通过体验降级来换来业务开发效率提升,我觉得是有其存在的意义的。
在业务层面上实现write once, working anywhere,在组件层面,可以使用各端最佳的方式实现,也未尝不可。
这次618的尝试,是个非常不错的开始,后续天猫在RN中的探讨,会更加深入,比如在React Native中添加Web的布局方式,让开发方式更灵活。在开发规范上,两边会做一些平衡等。
来源:Hugo Web前端开发