ReactMix:基于HTML+JS+CSS寫APP的最佳實踐
React Native官方支持iOS和Android,但并沒有覆蓋Web,有些人試圖做出補救,如兼容React Native API的ReactWeb,也有人用在React Native上再封裝一層的形式來兼容Web。ReacMix采取的就是后面的這種做法。ReactMix作者在QCon北京2016上對這個框架進行了分享,本文由演講總結整理而成。
嘉賓介紹
薛端陽,目前就職于上海攜程,機票事業部無線研發團隊高級技術經理,曾先后就職于焦點/淘寶/盛大/IBM/蘇寧/騰訊,在騰訊期間,主要負責騰訊電商旗下門戶網站易迅網前端架構,以及微信購物H5移動端,開源過前端UI框架KitJs,前端無限容量存儲方案localStore,前端多線程模板渲染引擎 MutiTpl,基于.Net非Node環境下的前后端首屏直出框架Fplus,以及最新基于React Native底層方案的ReactMix,跨平臺業務JS前臺解決方案等。
ReactMix 是在 React Native 和 ReactJS的基礎上,全新架構一層 Framework 和自動化翻譯工具,通過相應的翻譯機制和擴展模式,將現有的瀏覽器中可執行的 HTML 頁面、JS 代碼和 CSS 樣式,同步翻譯成為 React Native 可以執行的代碼,從而獲得在 App 上直接運行的能力,同時具備原生的 App 體驗的效果。
其Github地址為:https://github.com/xueduany/react-mix
ReactMix的誕生
ReactMix,看名字就知道與React相關,它可以從前端的思路上幫助開發者把現有的H5代碼平滑地轉換成ReactNative代碼。
React Native目前存在著幾方面的問題:第一,React Native目前只支持內聯的樣式,不支持我們常用的CSS className繼承和復用;第二,React Native采用了類似ReactJS的語法風格,要求有組件封裝,在組件A和組件B之間內部互相通信會比較麻煩;第三,對于已有的項目,如果改寫成React Native,就會有重構成本。這些問題都促進了ReactMix的誕生。
那么攜程要開發ReactMix呢?因為攜程90%的代碼是歷史代碼,我們希望能夠把這部分代碼很平滑的地變成React Native代碼。同時,我們希望能夠使用H5的特性,提高現有的代碼性能,包括渲染性能和執行性能。其實,最主要的原因是節省成本。綜合這些原因,我們開發了ReactMix,用來幫助我們很平滑地過渡到React Native,解決一套代碼完成H5、Android、iOS通用的問題。據說微軟也做了一個插件來支持UWP平臺,理論上ReactMix在這個插件下也是可以執行的。所以,可以說ReactMix可以通吃移動端了。
ReactMix第一印象
首先看一下ReactMix的代碼,如下圖所示。
(點擊放大圖像)
從圖中我們可以得出比較直觀的特點:第一,它的寫法類似JS;第二,采用了標記的寫法,去重構我們的頁面。而且,與阿里巴巴的Weex相比最大的不同是:Weex是單個class,而ReactMix不只是單個class。這樣一來,ReactMix就類似H5——H5里肯定有class name合并,兩個class name同時存在的時候才可以,這可以幫助程序員重構頁面。所以,第三個特點是它很像H5。
ReactMix如何支持CSS
ReactMix可以直接使用原始版本CSS,那么它是怎么用的呢?React Native在加載模塊的時候是通過關鍵字進行加載的:找到文件的路徑,使用require(),在運行的時動態解析require進行加載。ReactMix把這兩者結合在一起就成了第一個API,使用時可以給它起一個別名,編譯器會自動進行翻譯。它可以被動態地加載到瀏覽器運行環境中。但是在ReactMix里面只需要寫一個require,再加一個字符串,就可以引用這個文件。它的加載方式基于靜態語法分析,巧妙利用了React Native的require關鍵字是靜態編譯加載的特性。
ReactMix支持CSS屬性簡寫。React Native只是CSS文件的類別寫法,很多屬性必須明確其含義,不支持常用CSS的簡寫。有些屬性React Native幫你做了簡寫,有些沒做,所以需要有一個工具來做靜態語法分析。ReactMix提供了一個工具,可以動態監測CSS文件的變化,進行動態翻譯,翻譯成和該CSS文件在React Native中對應的JS文件。將這個翻譯后的JS文件給React Native用,而開發者在開發的時候看到的是一個CSS文件,也就是原始的文件。
除了簡寫,ReactMix還需要支持media query。那么ReactMix如何支持media query呢?media query中的屬性都是一個動態屬性,在執行上下文環境時候動態計算,得到當前頁面布局的結果。以media query舉例,要判斷屏幕是大屏還是小屏。需要在頁面開始渲染的時候有一個環境上下文,根據變量來去執行CSS里面的具體內容。ReactMix相當于對每個組件的方法進行重構,插入對于現有已支持的CSS文件動態解析,并根據這個動態解析內容,結合上下文環境,做到了對于media query選擇器做進一步的支持。ReactMix有一個動態語法分析,動態解析用戶所做的定義,在運行的時分別定義這些元素,做類似等價的實現換算。也就是運行時動態編譯。
上文提到的大部分內容是CSS文件加載以及CSS文件動態編譯,其實是基于兩種特性,一個特性是 靜態翻譯 ,二是 運行時翻譯 。那么還需要支持別的東西嗎?
我們經常會遇到CSS的基本單位、度量單位不統一的問題。例如,iPhone 5和iPhone 6相比,iPhone 6的屏幕要比iPhone 5要大。假如按照iPhone 5做尺寸布局,比如邊距定義為5,那么iPhone5上就會覺得小,而iPhone 6上字體就會顯得小。在iOS開發中,這被稱為適配工作。一般情況下iOS開發不需要做適配,內容隨著屏幕分辨率變大而變大,根據度量單位的方式來統一度量。有了這個度量單位,就可以去實現在三個端,或者未來四個端的整體布局方式,達到完全一樣的效果。
經過靜態翻譯和動態翻譯,以及CSS文件支持,再加上統一度量單位,最后渲染數出來的結果和在瀏覽器上看到的結果是一模一樣的。
前面提到,像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時經常被用到的例子。
如果需要去解析經CSS文件里面的單位要怎么做呢?首先在運行時候拿到頁面顯示的API,算出頁面的尺寸大小,然后做計算,生成按照當前屏幕大小動態顯示的值,最后把這個值放到上文提到的方法中,得到節點的屬性。
如何支持HTML
解決了支持CSS的問題之后,要解決HTML的問題。對于HTML來說,剛好可以對應到div和span、image可以實現對應的例子。但你需要去做什么事呢?在瀏覽器里面,image可以動態獲知圖片大小,根據圖片大小給它定義一定大小的容器,而在React Native里面不先定義大小是沒辦法顯示圖片的。使用ReactMix后,可以不需要知道圖片大小,它會根據圖片內容動態圖片信息,自動復制圖片大小,把這個方法包裝成同步的方法。
HTML節點的常用API允許直接寫自定義事件,同時得到事件的傳遞參數,和H5的標準事件是一樣的。那么這是怎么實現的呢?對于onclick和ontouch來說,首先統一做事件委托,定義好委托模型,然后在這些委托模型里面填充注冊文件。還需要利用冒泡機制,做上提操作,把很多事件統一放在一個大容器上,在處理事件時再判斷該觸發事件,而不是以就近的方式去處理。
ReactMix給每個元素去創建事件委托,在每個委托內,都抽象了對于標準事件的模擬,實現了類似domevent的對象,可以手動觸發它的冒泡機制。這里還有一個比較討厭的點,去找一個具體的元素話會有這樣一個問題:只能局限在component里面,如果把它分割過小就需要對component子元素進行修改,基于React Native來寫比較麻煩。需要對子元素做一個屬性,定義一個標簽,找到component A的節點。ReactMix可以基于sizzle選擇器找到這個節點,支持三種選擇器,可以通過整體的繼承關系去找到用戶想要的節點,返回的就是element對象。
ReactMix是怎么找到這個元素的呢?其實找到元素的方法有很多,在每個節點動態計數,如果用戶丟添加NodeId那么它不會添加,如果用戶沒添加那么它會動態添加ID。有了ID,它會注冊一個數組,把這個ID添加上去,可以根據數據節點找這個元素,也可以在里面找。如果根據classname來找也有對應的數據。剛才已經提到,ReactMix封裝了對象,可以判斷關系是否正確,根據這個關系可以找到用戶動態需要節點。
ReactMix創建了事件委托,可以提供標準的HTML的事件,可以支持標準的CSS文件和屬性,擴展了元素的實現。這些步驟全部做完之后,就可以把React Native做得類似于H5開發。剩下的問題就比較簡單了,就是API同步問題。React Native里面有一些API是異步的,但也有一些是同步的。在React Native里面,有一個異步API,那么對于瀏覽器來說,同樣一段代碼,比如用H5寫的同步代碼,來變成React Native代碼,那么需要async+await實現codestyle的同步。這需要增加一些成本,它是新增的語法,理解完之后還需要識別哪些API是同步的,哪些是異步的。這里需要加關鍵字,把異步代碼轉成同步代碼,ReactMix把異步的API放在關鍵字列表里面,動態編譯的時動態添加異步關鍵字,這對于開發人員完全透明。
關于第三方自定義控件
ReactMix基于ES7的語法,所以需要通過babel等工具做代碼降級。前文說的都是ReactMix基于加強方式做代碼,比如大家都使用的React Native,或者有一個項目引入了第三方插件,那么怎么實現這些框架之間的互相兼容呢。這就像兩只孫悟空,都是猴子,都是72變,它們的功能完全一樣的,只要不存在命名沖突,理論上就不會沖突。也就是說,它是語法插件,給用戶的是無侵入式的體驗。用戶可以在后臺使用React Native,也可以使用其他類似的實現。如果想基于H5方式,比如把現有H5代碼,以及JS+CSS代碼很平滑轉到別的平臺上,那么不用改任何代碼就可以把它很平滑地轉移過去。包括require也一樣,這個東西也可以動態根據不同平臺來定義不同的實現。
上線代碼會不會太大
前面這些問題都解決完之后,大家會思考:ReactMix提供了很多語法糖支持,轉了很多代碼,那么上線代碼會不會太大?這也是大家很關心的問題。
為了解決這個問題,我們會把上線代碼做一個拆分,拆分為兩個部分。第一部分是用戶要經常使用的一部分,包括H5本身的代碼,包括語法糖的代碼,它的大小是200K左右,加在一起差不多300K左右大小,大部分并行操作是靜態編譯實行的。對于真正的業務來說,它的包肯定更小,一般情況下是300K左右。比如要實現具體機票預定流程和航班動態查詢,加上CSS和JS文件,都加在一起差不多是這個大小。頁面的平均渲染時間差不多是iOS 200ms,Android 400ms,平均比hybrid加載速度快30%,我們希望能夠使用ReactNative幫助項目性能進行整體加速。ReactMix項目已經開源了,github地址是https://github.com/xueduany/react-mix ,里面包含目前已經完成的語法糖靜態編譯工具和運行時的編譯工具,大家可以關注一下。
來自: http://www.infoq.com/cn/articles/introducing-reactmix