近年來,伴隨着大前端概念的提出和興起,移動端和前端的邊界變得愈來愈模糊,涌現了一大批移動跨平臺開發框架和模式。從早期的PhoneGap、inoc等Hybird技術,到如今耳熟能詳的React Native、Weex和Flutter等技術,無不體現着移動端開發的前端化。而提供一套三端統一的開發框架,一直是前端奮鬥的目標,而React Native就是這麼一個不錯的三端統一的跨平臺開發框架,這方面的知識能夠參考我以前出的《React Native移動開發實戰》關於跨平臺相關的內容分析。html
注:本文原文地址開源的 React Native 組件庫前端
一個 React Native 應用的基礎組件庫,基於 0.53.3 版本,提供一整套開箱即用的高質量組件,包含 JS 組件和複合組件(包含 Native 代碼),涉及 FE、iOS、Android 三端技術,兼顧通用性和定製化,支持自定義主題,用於開發和服務企業級移動應用。開源地址:github.com/meituan/bee…node
據稱,beeshell已經被普遍使用在美團外賣的多條業務線,經過了各類業務場景、操做系統、機型的實戰考驗,具有很好的穩定性、安全性和易用性等特色,基於此,美團將此開源出來以供你們使用和借鑑。react
在beeshell開源以前,React Native社區已經出現了不少流行且著名的腳手架框架。此處選取 Github Star 數 5000 以上的組件庫,並從組件數量、通用性、定製化、是否包含原生功能、文檔完善程度五個維度來進行對比分析。git
組件庫 | 組件數量 | 通用性 | 定製化 | 是否包含原生功能 | 文檔完善程度 |
---|---|---|---|---|---|
react-native-elements | 16 | 強,提供一套風格一致的 UI 控件 | 弱,若要定製化可能須要重寫 | 否 | 高 |
NativeBase | 28 | 強,提供一套風格一致的 UI 控件 | 中,支持主題變量 | 是 | 高 |
ant-design-mobile | 41 | 強,提供一套風格一致的 UI 控件 | 中,部分能夠支持定製化需求 | 是 | 低 |
beeshell | 25 | 強,提供一套風格一致的 UI 控件 | 強,不只支持主題變量,還支持使用繼承的方式進行定製化擴展 | 是 | 高 |
經過對比能夠看出,beeshell 只在組件數量上稍有劣勢,在其餘方面都一致或者優於其餘項目。由於 beeshell 具有了良好的系統架構,因此豐富組件數量只時間問題,並且咱們團隊也已經有了詳細的規劃來完善數量上的不足。github
系統設計是將一個實際問題轉換成相應解決方案的主動過程,是解決辦法的描述。在通用的軟件工程模型中,需求分析完成後的第一步就是系統設計。一個項目最終的穩定性、易用性在很大程度上也取決於系統設計這一步。shell
beeshell 組件庫是爲了更加快速的搭建移動端應用,爲業務開發提供基礎技術支持,大幅提高開發人效。然而,面對不一樣的業務方、不一樣的功能需求、不一樣的 UI 規範與交互方式,如何有效的兼顧全部的需求?這對系統設計提出了更高的要求,下面以抽象層次逐層下降的方式來詳細介紹 beeshell 的系統設計。npm
beeshell 組件庫基於 React Native,向下經過 React Native 與 iOS、Android 平臺進行系統層面的交互,向上提供開發者友好的統一接口,抹平平臺差別,爲用戶開發業務功能提供服務支持。beeshell 扮演了一箇中間者的角色,從而保證了移動端應用基礎功能的穩定性、易用性。其框架的設計原理以下圖。編程
爲了更好的介紹beeshell,咱們來看一下beeshell設計上的一些細節。總體上使用 JS 做爲統一入口,多層封裝隱藏實現細節,抹平 JS 與 Native、iOS 平臺與 Android 平臺的差別,開箱即用,下降了用戶的學習和使用成本。局部上基於 React Native 的技術特色,分紅 JS 組件部分和複合組件部分,兩部分推行「鬆耦合」的開發模式,使得 Native 部分擁有替換變動的能力,提高組件庫的靈活性。 react-native
複合組件部分能夠直接暴露 JS 接口,若是有須要,也能夠在 JS 組件部分進行定製化封裝。咱們儘可能保證 Native 部分功能的原子性、簡潔性,有任何定製化需求都使用 JS 來統一實現,遵循 JS 實現優先的設計原則,保證跨平臺通用的特性。爲了達到上面的要求,下面從JS 組件部分和複合組件兩個部分來介紹。
一個軟件的設計分爲三個設計層次:體系結構、代碼設計和可執行設計。咱們使用自上而下的方法,從體系結構開始進行 JS 組件部分的設計。
軟件的體系結構的風格一般有 7 種:管道和過濾器,面向對象,隱式請求,層次化,知識庫,解釋程序和過程控制。
JS 組件部分使用了層次化的體系結構風格,總體分紅三層:基礎工具、通用組件、擴展組件,從上到下通用性逐漸減弱、定製化逐漸加強,功能漸進式加強,經過分層設計,各層各司其職,兼顧通用性和定製化。
咱們擴展組件部分會提供大量的定製化組件,若是仍然不能知足需求,用戶就能夠借鑑擴展組件的實現,根據本身業務需求,在某一繼承層級上繼承通用組件,自行進行定製化擴展,這點充分體現了 beeshell 定製化的能力。
既然是 React Native 組件庫固然少不了 Native 部分,複合組件包含 Native 的功能。beeshell 組件庫已經完成了 Native 部分的集成方案與規範,有良好的開發與使用體驗,能夠不斷的集成原生功能。
複合組件部分經過 JS 封裝接口,保證了跨平臺。Native 部分主要分紅 Native Bridge 和純 Native 兩大部分,Bridge 是針對 React Native 的封裝,必須在組件庫中實現;而純 Native 部分則能夠經過 Pods/Gradle 依賴三方實現,有效的吸取利用原生開發的技術積累。
React Native 提供了一些內置組件,咱們能使用 JS 來實現功能都是基於這些內置組件,這些內置的組件一些是跨平臺通用的組件,如:View、Text、TextInput;而另外一些是兩個平臺分別實現的,如 DatePickerIOS 和 DatePickerAndroid、AlertIOS 和 ToastAndroid。例如:
iOS 平臺的 DatePickerIOS 組件:
Android 平臺的 DatePickerAndroid 組件:
不只功能交互徹底不一樣,並且類名、調用方式各異,這不只知足不了業務需求,並且也有很高的學習和使用成本。這樣相似的組件還有不少,如何抹平平臺的差別,實現跨平臺?咱們提出的方案是優先使用 JS 來實現功能,這也是咱們組件庫的設計原則。針對上面的問題咱們開發了基於 ScrollView 的 Datepicker 組件,統一類名與調用方式,保證了跨平臺通用性。下面是Android和iOS實現的Datepicker組件。
Datepicker 是使用 JS 徹底實現了一個完整功能,可是有的狀況不須要實現完整的功能,咱們能夠經過 React Native 提供的 Platform 來進行局部的跨平臺處理。例如 TextInput 組件,默認在Android平臺下是沒有清空按鈕的,可是咱們能夠經過自定義來實現清空功能。
隨着移動互聯網的快速發展,各種移動端產品涌現而且不斷髮展,這也讓軟件知識不斷被普及,業務方對產品功能的定位逐漸從廠商主導轉變爲用戶主導。產品功能更加精準,個性化、細化、深化是必然趨勢,經過定製化服務來知足產品發展的要求也應運而生。不一樣行業、不一樣類型的產品,功能、特色各不相同,用某一種既定的軟件產品來知足不一樣類型的需求,其適用性可想而知。定製化有良好的技術架構和技術優點,可定製、可擴展、可集成、跨平臺,在個性化需求的處理方面,有着很好的優點,因此咱們須要定製化。
在組件庫設計之初,就已經統一好了 UI 規範。咱們根據 UI 規範,統必定義樣式變量並放置在基礎工具層中,即 beeshell/common/styles/varibles.js 文件中,在 React Native 應用中,樣式變量其實就是普通的 JS 變量,能夠很方便的進行復用與重寫操做。React Native 提供了 StyleSheet 經過建立一個樣式表,使用 ID 來引用樣式,減小頻繁建立新的樣式對象,在組件庫的樣式變量應用中靈活使用 StyleSheet.create 和 StyleSheet.flatten 來獲取樣式 ID 和樣式對象。
在每一個組的實現中,會事先引入基礎工具層中的樣式變量,使用統一的變量對象而不是在組件中自行定義,這樣就保證了 UI 樣式的一致性。同時,beeshell 提供了重置樣式變量的 API,能夠實現一鍵換膚。咱們推薦 beeshell 的用戶在開發移動應用時,事先定義好樣式變量。一方面使用本身的樣式變量重置 beeshell 的樣式變量;另外一方面在業務功能開發時,使用本身定義好的樣式變量,從而保證總體 UI 的一致性。
樣式定製化能夠從宏觀和總體的角度來實現,而功能的定製化則須要具體問題具體分析,從微觀和局部的角度來分析和實現。下文將以 Modal 系列的實現爲例,來詳細介紹功能定製化。
在移動端的彈窗交互,與 PC 端相比通常會比較簡單,咱們把模態框、下拉菜單、信息提示等交互相似的組件統一歸類爲 Modal 系列,使用繼承的方式實現。有人可能會問爲何使用繼承而不用使用組合?前文已經講過,組合的主要目的是代碼複用,而繼承的主要目的是擴展。考慮到彈窗交互有不少定製化的可能性,爲了知足更好的擴展性,咱們選擇了繼承的方式來實現。下面來看一下實現效果:
提供了遮罩、彈出容器以及淡入淡出(Fade)動畫效果,彈出內容部分徹底由用戶自定義。這個組件通用性極強,沒有任何定製化的功能。這裏須要說明下,動畫部分獨立實現,提供了 FadeAnimated 和 SlideAnimated 兩個子類,使用了策略模式與 Modal 系列集成,Modal 組件默認集成 FadeAnimated。
繼承 Modal 組件,對彈出內容作了必定程度的定製化擴展,支持標題、確認按鈕、取消按鈕以及自定義 body 部分的功能,通用性減弱,定製化加強。
經過以上部分,咱們已經對 Modal 系列已經有了直觀的認識,而後咱們來看下 Modal 系列的類圖以及分層。
React Native 應用的 JS 線程和 UI 線程是兩個線程,與瀏覽器中共用一個線程的實現不一樣,因此咱們能夠看到 React Native 提供的操做 UI 元素的 API,都是經過回調函數的方式進行調用。
受益於 React,咱們通常不須要直接操做 UI 元素,可是有的組件確實須要複雜的 UI 操做,例如徹底由 JS 實現的 Scrollerpicker 組件。
React Native 爲用戶提供了 style 屬性來控制元素的樣式,咱們能夠手動設置相關 UI 元素的尺寸。可是,在一些 Android 機器上,咱們設置的元素尺寸與 measure 方法獲取的尺寸信息不一致,通過大量 Android 機器的實際的測試,咱們獲得的結論是:有零點幾像素的偏差。
在使用 Form 組件時,最多見的需求就是校驗功能,一般組件庫的 Form 組件都會內置校驗功能。然而,由於校驗方式有同步與異步兩種,校驗結果展現的樣式、位置五花八門,這就致使了校驗功能的複雜度變得很高。
爲此,beeshell提供瞭如下幾種佈局方式: 絕對定位
何爲CVD,下面看一個模型。
每一層都對單一數據源 Store 進行不可變數據更新,符合交互內聚和順序內聚,內聚程度高。 每一層使用函數式組合的方式,定義 key(表單控件的惟一標誌)與 key 對應的回調函數,避免了批量 if else,能夠有效下降程序的圓環複雜度。
下面以 Input 組件錄入姓名爲例,來具體說明CVD的運做原理。
代碼的終極目標有兩個,第一個是實現需求,第二個是提升代碼質量和可維護性,測試是爲了提升代碼質量和可維護性,檢測代碼的質量。
單元測試(Unit Testing),是指對軟件中的最小可測試單元進行檢查和驗證。在結構化編程的時代,單元測試中單元指的就是函數。beeshell 組件庫全面使用單元測試,由組件的開發者完成。研究成果代表,不管何時做出修改都須要進行完整的迴歸測試,對於提供基礎功能的組件來講更是如此,在生命週期中儘早地對軟件產品進行測試將使效率和質量都獲得最好的保證。Bug 發現的越晚,修改它所需的成本就越高,單元測試是一個在早期抓住 Bug 的機會。beeshell 組件庫使用 Jest 作爲單元測試的工具,自帶斷言、測試覆蓋率工具,實現開箱即用。
測試用例的核心是輸入數據,咱們會選擇具備表明性的數據做爲輸入數據,主要有三種:正常輸入,邊界輸入,非法輸入。下面以組件庫中提供的 isLeapYear 工具函數來舉例說明。
函數使用了外部數據,正常輸入確定會有,這裏的 2000 和 '2000' 都是正常輸入;邊界輸入和非法輸入並非全部的函數都有,這裏爲了說明使用了有這兩種輸入的例子,邊界輸入是有效輸入的極限值,這裏 0 和 Infinity 是邊界輸入;非法輸入是正常取值範圍之外的數據, 'xx' 和 false 則是非法輸入。通常狀況下,考慮以上三種輸入能夠找出函數的基本功能點,單元測試與代碼編寫是「一體兩面」的關係,編碼時對上述三種輸入都是應該考慮的,不然代碼的健壯性就會出現問題。
上文所說的測試是針對程序的功能來設計的,就是所謂的「黑盒測試」。單元測試還須要從另外一個角度來設計測試數據,即針對程序的邏輯結構來設計測試用例,就是所謂的「白盒測試」。
白盒測試也是比較常見的需求,Jest 內置了測試覆蓋率工具,能夠直接在命令中添加 --coverage 參數即可以輸出單元測試覆蓋率的報告。
想要確保組件庫的 UI 不會意外被更改,快照測試(Snapshot Testing)是很是有用的工具。一個典型的移動 App 快照測試案例過程是,先渲染 UI 組件,而後截圖,最後和獨立於測試存儲的參考圖像進行比較。
使用 Jest 進行在快照測試,在 beeshell 中第一次對某個組件進行測試時,會在測試目錄下建立一個 snapshots 文件夾,並將快照結果存放在該文件夾中。快照結果文件以 <組件名>.js.snap 命名,其內容爲某個狀態下的 UI 組件樹。下面以 Button 組件爲例來講明如何使用快照測試。
運行命令後獲得快照結果以下:
常常與單元測試聯繫起來的開發活動還有靜態分析(Static analysis)。靜態分析就是對軟件的源代碼進行研讀,查找錯誤或收集一些度量數據,並不須要對代碼進行編譯和執行。
靜態分析效果較好並且快速,能夠發現 30%~70% 的代碼問題,能夠在幾分鐘內檢查一遍,成本低、收益高。beeshell 使用 SonarQube 進行靜態代碼檢查。
SonarQube 是一個開源的代碼質量管理系統,支持 25+ 種語言,能夠經過使用插件機制與 Eclipse、VSCode 等工具集成,實現對代碼的質量的全面自動化分析和管理。
SonarQube 經過對 Reliability(可靠性)、Security(安全性)、Maintainability(可維護性)、Coverage(測試覆蓋率)、Duplications(重複)幾個維度,對代碼進行全方位的分析,經過設置 Quality Gates 保證代碼質量。詳細的使用狀況能夠訪問SonarQube官網文檔
beeshell 組件庫使用 npm 包的形式下載使用,下載成功後會放置在項目根目錄的 node_modules 目錄,而後在項目中經過引入模塊的方式,引入 beeshell 的組件來使用。
那咱們如何開發組件庫?如何保證組件庫的開發與使用的體驗一致性?
首先,咱們須要一個 demo 項目,這個項目是 beeshell 組件庫的開發環境,是一個 React Native 應用。而後,咱們把 beeshell 作爲 demo 項目的依賴,在 demo 項目中下載安裝。如今,問題就變成了 node_modules 目錄中的 beeshell 如何和本地的 beeshell 源碼進行同步。
咱們知道,可使用 npm link 來開發 npm 包,其工做原理以下圖:
本質是就是使用 Symbol link,可是咱們創建好軟連接後,運行打包命令卻報錯了,錯誤信息爲 Expected path '/xxx/xxx/index.js' to be relative to one of project roots。
前端開發一般會用 Webpack 作爲打包工具,而 React Native 應用使用的是 Metro,因此此處咱們須要分析 Metro 來定位問題。
通過 Metro 的源碼分析,咱們發現 Metro 的打包方案與 Webpack 有較大差別,Webpack 是根據入口文件,即配置中的 entry 屬性,遞歸解析依賴,構建依賴關係圖而 Metro 是爬取特定路徑下的全部文件來構建依賴關係圖。分析發現 Metro 的特定路徑默認是運行打包命裏的路徑,以及 node_modules 下第一層目錄。
Metro 在爬取文件的時候,經過軟連接找到了全局的 beeshell 可是並無繼續判斷全局的 beeshell 是否有軟連接,因此沒法爬取 beeshell 源碼部分。
經過 ln -s 命令,直接創建 demo 項目 node_modules 下 beeshell 包 與 beeshell 源碼的軟連接。
本文爲轉載文章,原文地址:tech.meituan.com/waimai-bees…
附: beeshell開源地址