Qreact,去哪兒網的迷你react方案
去哪兒網在React Native深耕多年,對React內部實現的了解在國內應該是非常領先的。迫於項目對React體積的極致需求,我們推出了自己的迷你化方案——Qreact。
Qreact比市面上的其他迷你react框架的實用性更強,基本上可以說無縫切換到任何已有的react 15工程中,能極大地改善對體積的壓力,這在對流量非常苛刻的移動端上尤其重要!
我們從今年1月份快速啟動項目,在1個月內大致完成了功能,Demo,並配合現有的複雜例子進行驗收。本文將分享我們在做這輪子過程中的一些想法,包括競品分析,實現思路,項目風險控制等等。
一、核心需求我們並不是無事找事,為造輪子而造輪子。雖然有KPI的成分,但它的核心需求是來自業務線,可以說就算我們不造,公司內部其他人也會造一個,但那個質量可能無法保證,畢竟公司絕大部分的高手都分配到我們事業部。
由於沒有產品經理,我們需要充當產品經理的角色,聆聽業務線的需求,自己挖掘需求。
去哪兒深耕React多年,構建了兩個基於React的UI庫,它們都是用於移動端。如果這些庫都是內置在APP中,應該沒有要求。但是去哪兒分成十來個事業部,根據事業部的賺錢能力分配更新包的體積。為了能讓用戶在wifi上更新我們的APP,更新包的體積一般不超過100MB,因此像這樣公用的框架與庫體積越少越好。
此外,其中一個UI庫是用於手機瀏覽器上,我們稱之為React Web,用戶每次打開我們的頁面,都會載入一遍React與相關組件,這個對體積就更加敏感。因此當我們完成React Web,就著眼於迷你React的開發。
這個新的框架有三個核心需求:
1、體積小。移動端對體積一向敏感,因此在jQuery時代,zepto能割踞一方。
2、支持事件系統,這不是簡單add Event Listener,是React原來的那套Synthetic Event。它幫我們搞定300ms延遲,還有滾動列表時誤觸發點擊的問題。如果你不用它,你需要讓業務線參照iscroll的原理自造一個。
3、能直接替換。換言之,新框架與原框架的功能幾乎一致。因此許多業務已經
用React開發完畢,不希望做太多改動。由於業務線有時時間趕,碰到難題搞不定,會傾向用一些怪招歪招。在無法預料對方用什麼API的情況,新框架框架覆蓋原React的各種偏門用法。
下面是一個緊急修復的補丁:
圖1我們列舉一下各種偏門的API與用法
1、mixin包含mixin。這個在RN很常見。
2、ref, setState傳函數的用法
3、context與getChildContext的運用,雖然官方明確不建議大家用,但是著名的react-redux在源碼里用到了。
4、_rootNodeID, _hostParent,_hostNode這些內部屬性用在後端渲染與事件系統中。
二、競品分析
圖2在立項後,我們開始找市場上的同類產品,如果有滿足的,我們就不用開發了。目前,前端要找這些框架,只有一個去處,就是GITHUB。這是開源界的寶庫,應有盡有,琳琅滿目。
由於代碼公開,大家可以抄抄,因此每流行一樣的東西,大家都是一窩蜂上的。除開那些純練手的項目,每個庫都有自己獨到之處。
自從React推出虛擬DOM來解決複雜應用的性能問題以來,GITHUB上有上百個虛擬DOM的庫,包括之前的angular, vue2都在底層使用這種性能利器。
這是一些虛擬DOM框架或庫的數據,從相似度,性能,流行度,版本更新等情況綜合考慮,我們也只能選上面三者:inferno, preact, react-lite。
inferno從各方面來看,是無可挑剔的,性能比排行第二的kivi快20倍,更不用說vue,angular什麼之流。每個庫都會吹自己的框架有多快,但inferno的主頁上有大量測試頁面,是有真實數據支撐的。但是它偏面追求性能,源碼里的可讀性太差。看不懂,無從入手,只能遺憾地放棄了。
其他性能流有citijs, snabbdom, virtual-dom。最早搞出性能引擎的是citijs,然後基於它上面分化出kivi, ivi, snabbdom,然後vue2.0又直接將snabbdom庫整合到它裡面。virtual-dom則是走另一種性能優化方式。但它們都是迷你庫,API與React差太遠。
於是只剩下preact與react-lite。
三、設計思路由於是業務線的迫切需求,並且拖得越久,就越多項目用上RN,到時需要回歸測試的項目就越多,因此必須儘快搞出來。我們就不打算重造輪子,而是在已有輪子上改改。
第一版是基於react-lite。這是因為react-lite是攜程的工業聚大神寫的,攜程是我們的兄弟公司,應該比較好交流。但現實中發現,這個庫的擴展性不足,比如說事件系統那裡,需要傳入4個參數,在react-lite里只能拿到三個參數,想盡方法也無法湊齊第四參數。還有一些內部屬性,渲染流程與原裝React差得太遠了。在雙方折騰了2個星期後,我們組有人心灰意冷,著手後備方案,preact。
preact比起react-lite多出幾個優勢:
1、官方提供兼容補丁preact-compat
2、插件巨多
3、ISSUR活躍,當天提問題,大概到晚上,外國人起床就有回應了。
4、擴展方便
尤其第4點,在開發qreact時,我們都為雙方提了不少ISSUE。其實程序員還是比較靦腆,不願麻煩人,因此我們寫框架時還是多留一些擴展介面吧。
整個qreact的架構大概就是:
qreact= preact改+preact-compat改+react-web事件系統迷你版
在preact的源碼里一個叫options.js的文件,裡面有一個options的對象,它會被框架的多個關鍵方法調用。我們通過為它重新實現某些方法,就達到改寫框架的目標。
https://github.com/developit/preact/blob/master/src/options.js圖4兩個react-lite的難點問題,由於options的擴展機制太靈活了,一下子被擺平。
1、事件系統需要傳入4個參數的問題。在options添加一個handle Event方法。
2、內置屬性問題,在options重寫vnode方法。
重點說一下內部屬性問題:
圖5隨著版本的升級,這些內部屬性越來越多,這裡講解一下其中三個:
圖6這了讓preact支持它們,我們是在框架diff節點時,重新添加上它們的。因為這時,我們能輕鬆知道一個節點在DOM樹的上下關係。
最後是對事件系統進行瘦身。React有16000行,其中10000行都是事件系統相關的。再加上React Native中的Pan Responder系統。這體積非常龐大。但是如果我們將要支持的瀏覽器收窄一點,不支持IE系列與firefox系列。起碼在事件對象的構造器上,我們可以做一些合併操作。
下面React中的事件構造器列表:
facebook/react它們濃縮成一個事件構造器後,代碼少了3000行。
我們再對事件插件進行圍剿。因為我們不需要mouseenter, mouseleave, input, composition,beforeinput的兼容,又可以減少許多行。
facebook/react
最後成果是 qreact縮少到6000行,事件系統占其中的4000行,min後的體積為39kb。原版React的min體積是140kb。減少近80kb。
體積算是達標了,那麼性能如何呢?畢竟我們使用React的初衷是因為它的性能太好了。React的性能主要來自它的虛擬DOM的diff演算法。體積縮水了,它的diff演算法肯定也打折扣。這時preact提出兩個後備方案:
1、減少要比較的虛擬DOM的數量 hydrate。這是發端於inferno的優化方案,通過合併相鄰的字元串或數字,減少虛擬DOM數量。
圖72、減少要生成的真實DOM的數據recycle。上面的hycycle也會減少真實DOM的數量,但我們還可以將要移除的真實DOM保存起來,重複利用這些真實DOM。
通過這兩種機制,大大彌補qreact diff演算法的缺憾。此外,我們還可以通過動靜分離的方式來提高性能。在定義JSX時,我們就能得知某個元素是否包含花括弧,有花括弧說明其是動態的,反之是靜態的,但一個元素與其所有子孫都沒有花括弧,那麼這個子樹可以整體緩存起來,以後轉換為真實DOM後,它能緩存起來。
然後在組件的render方法中,對於這部分的React Element每次返回相同的對象,並且在上面添加一個標記,碰到兩個對象都有這個標記,就直接返回,不往下比較了。這是inferno提出的另一個性能優化方案。
最後驗證性能是用ListView進行測試的,和原來一樣流暢。
四、分享展示
aHR0cDovL3VlZC5xdW5hci5jb20vcXJlYWN0 (二維碼自動識別)圖8
裡面最重要的兩個例子就是yo-demo與qunar-react-native-web
【作者簡介】鍾欽成,網名司徒正美,著名的JavaScript專家,去哪兒網前端架構師。在GITHUB擁有複數個著名的輪子,著有《javascript框架設計》一書。本文來自司徒正美在「攜程技術沙龍——新一代前端技術實踐」上的分享。
沒看夠?更多來自攜程技術人的一手乾貨,歡迎搜索關注「攜程技術中心」微信公號哦~
推薦閱讀:
※React.js: web開發者的14個工具和資源
※React Fiber初探
※通過 JSX Control Statements 編寫 JSX
※為何需要Angularjs、backbonejs、reactjs?
※Vue2技術棧歸納與精粹
TAG:React |