React Web:讓React Native代碼跑在Web上
說明:公司項目尚未開源,本文僅闡述實現方案,不涉及具體技術實現細節,見諒。
===============正文分割線=============
前言
非死book發布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,
、 等。 | -
完全向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:
轉換到Web
RW:
Border轉換
RN :
轉換到Web :
Style的兼容,主要是Flex布局的兼容,其他都是比較小的細節點,沒有多大難度。
前期的原型demo
component補齊
component補齊是件非常有意思的事情,相當于有個架構師幫你把接口定義好了,你只需要實現對應的接口行為就行。
有2個組件,是整個RW轉換最為關鍵,也是最有難度的組件:ScrollView、ListView 。剛好這方面,淘寶同學開發的xScroll與這兩個native組件的理念非常接近。并且xscroll本身的實現也參考了Native中UIScrollView的接口。
也舉個適配的例子:
Text組件中有個props numberOfLines 其表現的行為是多行文本截斷。
在web端,可通過css來控制:
其最終比較效果:
View組件的效果,效果對比圖:
API補齊
API的補齊,相對比較簡單,沒有太難實現的API,并且很多API均來自Web。比如:requestAnimationFrame、Promise。
其中比較特別的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的實現,其最大的難度在于性能優化。這也許是非死book不推崇write once, working anywhere的主要原因。
那么 RW的實現是否有意義,這取決于業務的側重點,如果80%的流量來源于APP,那么在web端,通過體驗降級來換來業務開發效率提升,我覺得是有其存在的意義的。
在業務層面上實現write once, working anywhere,在組件層面,可以使用各端最佳的方式實現,也未嘗不可。
這次618的嘗試,是個非常不錯的開始,后續天貓在RN中的探討,會更加深入,比如在React Native中添加Web的布局方式,讓開發方式更靈活。在開發規范上,兩邊會做一些平衡等。
來源:Hugo Web前端開發