ReactMix:基于HTML+JS+CSS写APP的最佳实践

fp8675 8年前
   <p>React Native官方支持iOS和Android,但并没有覆盖Web,有些人试图做出补救,如兼容React Native API的ReactWeb,也有人用在React Native上再封装一层的形式来兼容Web。ReacMix采取的就是后面的这种做法。ReactMix作者在QCon北京2016上对这个框架进行了分享,本文由演讲总结整理而成。</p>    <p>嘉宾介绍</p>    <p>薛端阳,目前就职于上海携程,机票事业部无线研发团队高级技术经理,曾先后就职于焦点/淘宝/盛大/IBM/苏宁/腾讯,在腾讯期间,主要负责腾讯电商旗下门户网站易迅网前端架构,以及微信购物H5移动端,开源过前端UI框架KitJs,前端无限容量存储方案localStore,前端多线程模板渲染引擎 MutiTpl,基于.Net非Node环境下的前后端首屏直出框架Fplus,以及最新基于React Native底层方案的ReactMix,跨平台业务JS前台解决方案等。</p>    <p>ReactMix 是在 React Native 和 ReactJS的基础上,全新架构一层 Framework 和自动化翻译工具,通过相应的翻译机制和扩展模式,将现有的浏览器中可执行的 HTML 页面、JS 代码和 CSS 样式,同步翻译成为 React Native 可以执行的代码,从而获得在 App 上直接运行的能力,同时具备原生的 App 体验的效果。</p>    <p>其Github地址为:https://github.com/xueduany/react-mix</p>    <h2>ReactMix的诞生</h2>    <p>ReactMix,看名字就知道与React相关,它可以从前端的思路上帮助开发者把现有的H5代码平滑地转换成ReactNative代码。</p>    <p>React Native目前存在着几方面的问题:第一,React Native目前只支持内联的样式,不支持我们常用的CSS className继承和复用;第二,React Native采用了类似ReactJS的语法风格,要求有组件封装,在组件A和组件B之间内部互相通信会比较麻烦;第三,对于已有的项目,如果改写成React Native,就会有重构成本。这些问题都促进了ReactMix的诞生。</p>    <p>那么携程要开发ReactMix呢?因为携程90%的代码是历史代码,我们希望能够把这部分代码很平滑的地变成React Native代码。同时,我们希望能够使用H5的特性,提高现有的代码性能,包括渲染性能和执行性能。其实,最主要的原因是节省成本。综合这些原因,我们开发了ReactMix,用来帮助我们很平滑地过渡到React Native,解决一套代码完成H5、Android、iOS通用的问题。据说微软也做了一个插件来支持UWP平台,理论上ReactMix在这个插件下也是可以执行的。所以,可以说ReactMix可以通吃移动端了。</p>    <h2>ReactMix第一印象</h2>    <p>首先看一下ReactMix的代码,如下图所示。</p>    <p>(点击放大图像)</p>    <p><img src="https://simg.open-open.com/show/fa487dbf5c364ba909e9a72edac618b5.jpg"></p>    <p>从图中我们可以得出比较直观的特点:第一,它的写法类似JS;第二,采用了标记的写法,去重构我们的页面。而且,与阿里巴巴的Weex相比最大的不同是:Weex是单个class,而ReactMix不只是单个class。这样一来,ReactMix就类似H5——H5里肯定有class name合并,两个class name同时存在的时候才可以,这可以帮助程序员重构页面。所以,第三个特点是它很像H5。</p>    <h2>ReactMix如何支持CSS</h2>    <p>ReactMix可以直接使用原始版本CSS,那么它是怎么用的呢?React Native在加载模块的时候是通过关键字进行加载的:找到文件的路径,使用require(),在运行的时动态解析require进行加载。ReactMix把这两者结合在一起就成了第一个API,使用时可以给它起一个别名,编译器会自动进行翻译。它可以被动态地加载到浏览器运行环境中。但是在ReactMix里面只需要写一个require,再加一个字符串,就可以引用这个文件。它的加载方式基于静态语法分析,巧妙利用了React Native的require关键字是静态编译加载的特性。</p>    <p><img src="https://simg.open-open.com/show/6e4b61245e14513397ea424feff057be.jpg"></p>    <p>ReactMix支持CSS属性简写。React Native只是CSS文件的类别写法,很多属性必须明确其含义,不支持常用CSS的简写。有些属性React Native帮你做了简写,有些没做,所以需要有一个工具来做静态语法分析。ReactMix提供了一个工具,可以动态监测CSS文件的变化,进行动态翻译,翻译成和该CSS文件在React Native中对应的JS文件。将这个翻译后的JS文件给React Native用,而开发者在开发的时候看到的是一个CSS文件,也就是原始的文件。</p>    <p>除了简写,ReactMix还需要支持media query。那么ReactMix如何支持media query呢?media query中的属性都是一个动态属性,在执行上下文环境时候动态计算,得到当前页面布局的结果。以media query举例,要判断屏幕是大屏还是小屏。需要在页面开始渲染的时候有一个环境上下文,根据变量来去执行CSS里面的具体内容。ReactMix相当于对每个组件的方法进行重构,插入对于现有已支持的CSS文件动态解析,并根据这个动态解析内容,结合上下文环境,做到了对于media query选择器做进一步的支持。ReactMix有一个动态语法分析,动态解析用户所做的定义,在运行的时分别定义这些元素,做类似等价的实现换算。也就是运行时动态编译。</p>    <p>上文提到的大部分内容是CSS文件加载以及CSS文件动态编译,其实是基于两种特性,一个特性是 <strong>静态翻译</strong> ,二是 <strong>运行时翻译</strong> 。那么还需要支持别的东西吗?</p>    <p>我们经常会遇到CSS的基本单位、度量单位不统一的问题。例如,iPhone 5和iPhone 6相比,iPhone 6的屏幕要比iPhone 5要大。假如按照iPhone 5做尺寸布局,比如边距定义为5,那么iPhone5上就会觉得小,而iPhone 6上字体就会显得小。在iOS开发中,这被称为适配工作。一般情况下iOS开发不需要做适配,内容随着屏幕分辨率变大而变大,根据度量单位的方式来统一度量。有了这个度量单位,就可以去实现在三个端,或者未来四个端的整体布局方式,达到完全一样的效果。</p>    <p>经过静态翻译和动态翻译,以及CSS文件支持,再加上统一度量单位,最后渲染数出来的结果和在浏览器上看到的结果是一模一样的。</p>    <p><img src="https://simg.open-open.com/show/1337f2b776da345c7f13f58b73fe0cc0.jpg"></p>    <p>前面提到,像React Native只支持内联的CSS。假如要实现这样一个例子:class A和class B,最终节点渲染需要往下查父节点,判断最终节点里有哪些class属性。具体来说,有个节点,它有classname属性,在CSS文件里定义可能就是class A,要满足父节点包含class B,父父节点包含class C。对于ReactMix来说,如果直接按照class A、B、C的方式不能做继承关系,只能做组合关系,即以ABC三个样式组合在一起,可以看到样式组合。这个时候要以最终生效的节点,即class C,做个标记。比如做成向上的小箭头,后面跟着它的值,组合成一个k-v map,然后定义在classB之前有哪些限制条件。然后再对这个节点做向上查找,看副节点是否满足包含class的且是最后元素。如果包含取出来节点包含该className,就认为它满足条件,直到查找到根节点。这是在写H5时经常被用到的例子。</p>    <p>如果需要去解析经CSS文件里面的单位要怎么做呢?首先在运行时候拿到页面显示的API,算出页面的尺寸大小,然后做计算,生成按照当前屏幕大小动态显示的值,最后把这个值放到上文提到的方法中,得到节点的属性。</p>    <h2>如何支持HTML</h2>    <p>解决了支持CSS的问题之后,要解决HTML的问题。对于HTML来说,刚好可以对应到div和span、image可以实现对应的例子。但你需要去做什么事呢?在浏览器里面,image可以动态获知图片大小,根据图片大小给它定义一定大小的容器,而在React Native里面不先定义大小是没办法显示图片的。使用ReactMix后,可以不需要知道图片大小,它会根据图片内容动态图片信息,自动复制图片大小,把这个方法包装成同步的方法。</p>    <p>HTML节点的常用API允许直接写自定义事件,同时得到事件的传递参数,和H5的标准事件是一样的。那么这是怎么实现的呢?对于onclick和ontouch来说,首先统一做事件委托,定义好委托模型,然后在这些委托模型里面填充注册文件。还需要利用冒泡机制,做上提操作,把很多事件统一放在一个大容器上,在处理事件时再判断该触发事件,而不是以就近的方式去处理。</p>    <p>ReactMix给每个元素去创建事件委托,在每个委托内,都抽象了对于标准事件的模拟,实现了类似domevent的对象,可以手动触发它的冒泡机制。这里还有一个比较讨厌的点,去找一个具体的元素话会有这样一个问题:只能局限在component里面,如果把它分割过小就需要对component子元素进行修改,基于React Native来写比较麻烦。需要对子元素做一个属性,定义一个标签,找到component A的节点。ReactMix可以基于sizzle选择器找到这个节点,支持三种选择器,可以通过整体的继承关系去找到用户想要的节点,返回的就是element对象。</p>    <p>ReactMix是怎么找到这个元素的呢?其实找到元素的方法有很多,在每个节点动态计数,如果用户丢添加NodeId那么它不会添加,如果用户没添加那么它会动态添加ID。有了ID,它会注册一个数组,把这个ID添加上去,可以根据数据节点找这个元素,也可以在里面找。如果根据classname来找也有对应的数据。刚才已经提到,ReactMix封装了对象,可以判断关系是否正确,根据这个关系可以找到用户动态需要节点。</p>    <p>ReactMix创建了事件委托,可以提供标准的HTML的事件,可以支持标准的CSS文件和属性,扩展了元素的实现。这些步骤全部做完之后,就可以把React Native做得类似于H5开发。剩下的问题就比较简单了,就是API同步问题。React Native里面有一些API是异步的,但也有一些是同步的。在React Native里面,有一个异步API,那么对于浏览器来说,同样一段代码,比如用H5写的同步代码,来变成React Native代码,那么需要async+await实现codestyle的同步。这需要增加一些成本,它是新增的语法,理解完之后还需要识别哪些API是同步的,哪些是异步的。这里需要加关键字,把异步代码转成同步代码,ReactMix把异步的API放在关键字列表里面,动态编译的时动态添加异步关键字,这对于开发人员完全透明。</p>    <h2>关于第三方自定义控件</h2>    <p>ReactMix基于ES7的语法,所以需要通过babel等工具做代码降级。前文说的都是ReactMix基于加强方式做代码,比如大家都使用的React Native,或者有一个项目引入了第三方插件,那么怎么实现这些框架之间的互相兼容呢。这就像两只孙悟空,都是猴子,都是72变,它们的功能完全一样的,只要不存在命名冲突,理论上就不会冲突。也就是说,它是语法插件,给用户的是无侵入式的体验。用户可以在后台使用React Native,也可以使用其他类似的实现。如果想基于H5方式,比如把现有H5代码,以及JS+CSS代码很平滑转到别的平台上,那么不用改任何代码就可以把它很平滑地转移过去。包括require也一样,这个东西也可以动态根据不同平台来定义不同的实现。</p>    <h2>上线代码会不会太大</h2>    <p>前面这些问题都解决完之后,大家会思考:ReactMix提供了很多语法糖支持,转了很多代码,那么上线代码会不会太大?这也是大家很关心的问题。</p>    <p>为了解决这个问题,我们会把上线代码做一个拆分,拆分为两个部分。第一部分是用户要经常使用的一部分,包括H5本身的代码,包括语法糖的代码,它的大小是200K左右,加在一起差不多300K左右大小,大部分并行操作是静态编译实行的。对于真正的业务来说,它的包肯定更小,一般情况下是300K左右。比如要实现具体机票预定流程和航班动态查询,加上CSS和JS文件,都加在一起差不多是这个大小。页面的平均渲染时间差不多是iOS 200ms,Android 400ms,平均比hybrid加载速度快30%,我们希望能够使用ReactNative帮助项目性能进行整体加速。ReactMix项目已经开源了,github地址是https://github.com/xueduany/react-mix ,里面包含目前已经完成的语法糖静态编译工具和运行时的编译工具,大家可以关注一下。</p>    <p>来自: <a href="/misc/goto?guid=4959674865001460027" rel="nofollow">http://www.infoq.com/cn/articles/introducing-reactmix</a></p>    <p> </p>