说明:公司项目尚未开源,本文仅阐述实现方案,不涉及具体技术实现细节,见谅。

===============正文分割线=============

前言

Facebook发布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,<Flex> 、<Cell> 等。

  • 完全向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:

var styles = StyleSheet.create({
    view : {
        flex : 1
    }
})


<View style={{flex:1}}><Image /></View>

转换到Web

RW:

View 的父级css :

display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;

View的css :

flex: 1;
-webkit-box-flex: 1;
-webkit-flex: 1;
-ms-flex: 1;

<View style={{flex:1}}><Image /></View>

Border转换

RN :

borderColor : #cccccc 
borderWidth : 1

转换到Web :

borderColor : #cccccc
borderWidth : 1
border-style: solid;

Style的兼容,主要是Flex布局的兼容,其他都是比较小的细节点,没有多大难度。

前期的原型demo

component补齐

component补齐是件非常有意思的事情,相当于有个架构师帮你把接口定义好了,你只需要实现对应的接口行为就行。

有2个组件,是整个RW转换最为关键,也是最有难度的组件:ScrollView、ListView 。刚好这方面,淘宝同学开发的xScroll与这两个native组件的理念非常接近。并且xscroll本身的实现也参考了Native中UIScrollView的接口。

也举个适配的例子:

Text组件中有个props numberOfLines 其表现的行为是多行文本截断

在web端,可通过css来控制:

//组件加载前调用
componentWillMount(){
    //如果是多行截断的话则使用css3来实现
    if(this.props.numberOfLines){
        this.props.style['overflow'] = 'hidden';
        this.props.style['display'] = '-webkit-box';
        this.props.style['WebkitLineClamp'] = this.props.numberOfLines;
        this.props.style['WebkitBoxOrient'] = 'vertical';
    }
},

其最终比较效果:

View组件的效果,效果对比图:

API补齐

API的补齐,相对比较简单,没有太难实现的API,并且很多API均来自Web。比如:requestAnimationFramePromise

其中比较特别的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 的实现,其最大的难度在于性能优化。这也许是Facebook不推崇write once, working anywhere的主要原因。

那么 RW的实现是否有意义,这取决于业务的侧重点,如果80%的流量来源于APP,那么在web端,通过体验降级来换来业务开发效率提升,我觉得是有其存在的意义的。

在业务层面上实现write once, working anywhere,在组件层面,可以使用各端最佳的方式实现,也未尝不可。

这次618的尝试,是个非常不错的开始,后续天猫在RN中的探讨,会更加深入,比如在React Native中添加Web的布局方式,让开发方式更灵活。在开发规范上,两边会做一些平衡等。

One thought on “React Web: 让React Native 代码跑在Web上

  1. Pingback: React Web:让React Native代码跑在Web上 – M.W

Leave a reply

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> 

required