去哪儿网迷你React的研发心得

去哪儿网迷你React是年初立项的新做品,在这前,去哪儿网已经深耕多年,拥有QRN(react-native的公司制定版),HY(基于React的hybird方案), yo(基于React的移动UI库),QRN-web(基于React的三端合一移植方案),此外,像机票等部门也大规模将React用于前台页面,后台页面就更不在话下。javascript

如此普遍地应用React,咱们熟晓其优缺点。优势是代码的可维护性大大提升,性能卓然!但缺点也明显,因为体积太大,React.js+React-DOM.js超过3万行,体量过3MB,已经加上immutable.js , redux, redux-react, react-router等全家桶,工程师一行代码没有写,已经好几MB了。这在过去绝对不可想象,要知道,体积意味着流量,流量表明着金钱,越大越烧钱,越大下载速度越慢,用户体验越差,用户就会流失。如今问题摆在咱们面前,咱们就得解决。虽然webpack官网有各类瘦身方案,但瘦死的大象也比马大。这是一个如此广泛存在的问题,所以外国人确定也遇到过,思考过,提出什么新点子——全部这一切,都指向一个名词,“迷你React”。前端

做为一个生态圈成熟的标志,一个库出名了,就各类偏门补丁,闪光效果往上加,库不免会膨胀,不爽的人就会推出迷你化方案。上一个世代是jQuery与zepto。React的迷你方案也有很多,preact, inferno, react-lite, dio, rax……vue

clipboard.png

但问题不是简单到直接从github找一个迷你React库替换上就能搞定。之因此称为迷你,确定有所欠缺,若是是内部实现的改良也罢,若是阉割了功能可不是闹着玩。偏偏他们就好这口,所以现有方案不能知足咱们。咱们须要一个能直接替换,或至少95%的业务代码不用动。这意味着这迷你框架,须要与React的接口彻底一致,而且全面支持它的全家桶。固然细化一下来的需来就更多了,早期说只要支持移动,所以取名为react-mobile,后来连iE8也要支持,所以这活不能期望别人,咱们本身动手撸了。java

clipboard.png

咱们先来一趟竞品分析。为了加快产出速度,能借鉴的尽量借鉴。React推出之后,针对其性能研究衍生出很多库,针对其体积也诞生出大量仿品。它比jQuery更加缤纷多采。市面上居然拥有100多个虚拟DOM库。虚拟DOM库,就是React出来后的一种新式库,以虚拟DOM与diff算法为核心,屏蔽DOM操做,操做数据即操做视图。这听起来有点像MVVM,MVVM也是屏蔽DOM操做,操做数据即操做视图,但它是以VM为核心。react

clipboard.png

React及其余虚拟DOM库已经将虚拟DOM的生成交由JSX与babel处理了,所以不一样点是,虚拟DOM的结构与diff算法。虚拟DOM万宗不离其变是三大属性,type, props, children,固然也能够改一下别名,babel能够作相应配置。此外,虚拟DOM能够加入更多冗余标识,以帮diff算法的改良。webpack

React最初推出时也不火,那时的招牌与如今性能不什么两样,也是高性能。可是JSX离经背道,与业界宣扬了多年的先后端分离,数据结构样式分离等教条差太远了,一直默默在角落里画圈。直到RN出来,解决原生编写界面的痛苦才一炮而红。你们才留意它的性能,它的性能背后的diff算法。最先研究React的diff算法是virtual-dom这个库,是基于经典的DFS算法。后来相应的算法就多起来。最后才是从接口进行模拟,就是所谓的React-like框架。所以虚拟DOM库是分为两大派系:算法派与拟态派。git

clipboard.png

下面将从这两大派系进行扼要的描述。github

将前端回归到算法上的探索是前端框架史上的一个巨大进步。以前是MVVM将数据从繁复的DOM操做分离出来。正由于有了纯数据,而且数据结构可控,那么算法才有发挥的余地。web

最开始出现的是 virtual-dom这个库,是你们好奇React为何这么快而搞鼓出来的。它的实现是很是学院风格,经过深度优先搜索与in-order tree来实现高效的diff。它与React后来公开出来的算法是很不同。算法

而后是cito.js的横空出世,它对从此全部虚拟DOM的算法都有重大影响。它采用两端同时进行比较的算法,将diff速度拉高到几个层次。

紧随其后的是kivi.js,在cito.js的基出提出两项优化方案,使用key实现移动追踪及基于key的编辑长度矩离算法应用(算法复杂度 为O(n^2))。

但这样的diff算法太过复杂了,因而后来者snabbdom将kivi.js进行简化,去掉编辑长度矩离算法,调整两端比较算法。速度略有损失,但可读性大大提升。再以后,就是著名的vue2.0 把sanbbdom整个库整合掉了。

固然算法派的老大是inferno,它使用多种优化方案将性能提至最高,所以其做者便邀请进react core team,负责react的性能优化了。这个我后面会详细。

clipboard.png

再看拟态派。React的接口并很少,可是其组件的实现是至关有难度。它的生命周期是如何运做,须要对源码有深入的理解,所以它们出来得比较晚。咱们的学习对象也就是它们几个。

clipboard.png

先说虚拟DOM。虚拟DOM就是一个普通的JS对象,一般拥有三个属性,type, props, children。但无状态组件出来后,children改放到props中。此外,有些元素还有ref属性,能够是字符串与函数。在数组里,为了提升diff速度,又多出了key属性。bable会将JSX这些属性转换为一个VNode对象。这是虚拟DOM的最小单元。全部虚拟DOM会组成一棵树,叫虚拟DOM树。

为了防止每次都是整个树进行diff,须要造成子树的概念,因而出现组件了。组件有render方法,会返回一个虚拟DOM,这个虚拟DOM及其子孙,就造成一棵子树。但render方法不是虚拟DOM的东西,因而咱们规定当虚拟DOM为一个函数时,若是这个函数继承于React.Component,这个方法的实例必须有render方法。因而咱们就像虚拟DOM与组件统合起来了。或者衍生这两个称呼,原子虚拟DOM与组件虚拟DOM。原子虚拟DOM对应元素节点,而组件则是用来产出原子虚拟DOM。此外,原子虚拟DOM还能包含一些东西,字符串与数字与null。字符串与数字对应文本节点,null对应注释节点。

clipboard.png

一个组件虚拟DOM实例化为组件后,会返回原子虚拟DOM或另外一个组件虚拟DOM。这就造成函数式编程上的高阶函数的机制,所以进行出无状态函数组件,就是虚拟DOM的type属性就是一个函数,不继承其余东西了。

组件虚拟DOM的实例化过程是很是复杂,若是能简化这过程,简化其结构,这性能就上去了。

clipboard.png

此外组件的实例自己就巨耗性能,所以官方推荐页面的结构以下,经过最少许的有状态组件(smart component)控制无状态组件(dumb component)的变化,全部状态经过redux在路由进行分发。

clipboard.png

通过一番比较后,咱们着手开发本身的迷你React。这个过程也比较坎坷,这仍是有前人参照物的状况下,可想而知,当初facebook开发出React这样一个独行特立的框架时,是多么艰辛。咱们在这半年总共搞了三个东西,第一还孵化失败。

clipboard.png

最初是基于react-lite,考虑时当时是咱们母公司的人搞的,方便交流。但后来发现它的事件系统太鸡肋,难以扩展,最后放弃了。

第二代是基于preact,代号qreact, 国内也有许多公司基于它作本身的迷你化方案,由于官方提供了一个preact-compat的模块。可是preact-compat是使用Object.defineProperty来实现一些属性名映射与同步的,所以不支持IE8,而且使用了Object.defineProperty会严重拖慢速度。preact自己也有很多BUG,最著名的有三个,生命周期的unmount钩子不能保证在mount以前执行,元素节点的重复利用没有清理样式会致使出错,同一组孩子下可能存在同名的key致使排序失败。这些咱们都为官方提issue,而且在咱们的版本中进行修复了。第二代也公司内部几个项目中试水落地,反映不错。

第三代是咱们团队独力开发,a内部代号anu,正式名称仍然是 QReact,不过演进到了 QReact 1.0 了 (如今的版本使用了 1.0 大版本,由于以前这个版本没被占用,因此没跳到 2.0)。在对preact缝缝补补的过程,掌握很多核心知识,新的框架是使用全新的算法,全新的结构。因为不使用高级API,理论上能支持到IE6,但咱们公司只需支持到IE8。

Qreact1.0使用requestAnimationFrame来稳定它的运行帧数,保证在60帧每秒的流畅速度。因为bable会对type进行打补丁,内部统一用typeNumber代替type进行类型断定。使用列队保证生命周期钩子按顺序执行。使用__rerender标识一个组件在一次大的更新只会被render一次。凡此种种,通过大量测试,它们的接口与React别无二致,甚至React废弃的接口createClass, PropTypes,咱们都有相应的polyfill。

下面是Qreact1.0的测试数据。
从性能上,直追preact。

clipboard.png

从体积上,是官方react的1/4至1/5之间。

clipboard.png

这里透露一下React性能爆表的秘密,除了diff算法外,setState的合并操做也是一个关键。当组件没有插入到DOM树前,用户在componentWillMount方法屡次执行setState,这些state是不会触发更新,而是存放到一个数组中,而后在render方法里进行合并与应用。当一个组件进行更新,多是用户在componentDidMount或者事件回调执行setState,这时更新是即时的,同步的,但这之次再setState,它就不会更新了,它的state会进入列队。此外,若是用户在componentWillReceiveProps屡次执行setState,也会产生延迟。React这种行为是保证页面的更新次数最少,同时用户不会察觉它没有更新。它只是将state进行了合并。Qreact1.0是彻底遵循了这些规则,从而实现了高性能。

而在体积上,则经过删除一些对线上没用的代码实现迷你化效果。

clipboard.png

但仅是这样不足以大吹特吹了。为了超越React的性能,从inferno与preact借签了很多手段。

clipboard.png

最值得一提的是hydrate机制,经过合并相邻字符串,从而减小文本节点的生成,从而减小diff次数。

clipboard.png

最后还有一个压轴大戏,不作测试不知道,原来高级API是如何耗性能。经过去掉Object.freeze, Object.defineProperty这些es5方法, 框架就有10帧的提高!

clipboard.png

说了这么多,来些实际可运行的例子吧。目前,Qreact跑通内部几套测试,已经在金融与大搜的项目使用。它的第二版也在机票一个拥有820个模块的大项目中试水,在IE8下良好运行。

clipboard.png

若是你们想在项目中使用qreact,能够在webpack或ykit的config中以下配置:

resolve: {
   alias: {
      'react': 'anujs',
      'react-dom': 'anujs',
        // 若要兼容 IE 请使用如下配置
        // 'react': 'anujs/dist/ReactIE',
        // 'react-dom': 'anujs/dist/ReactIE',
    
        // 若是引用了 prop-types 或 create-react-class
        // 须要添加以下别名
        'prop-types': 'anujs/lib/ReactPropTypes',
        'create-react-class': 'anujs/lib/createClass'
        //若是你在移动端用到了onTouchTap事件
        'react-tap-event-plugin': 'anujs/lib/injectTapEventPlugin',  
   }
},

若是你们对qreact在感兴趣,欢迎与我联系,也欢迎你们为个人框架加星。

https://github.com/RubyLouvre/anu/
https://github.com/YMFE/qreact

相关文章
相关标签/搜索