框架設計:RN 端的框架如何設計與落地

著做權歸做者全部。商業轉載請聯繫 Scott 得到受權,非商業轉載請註明出處[務必保留全文,勿作刪減]。

線下越重,線上須要越輕,這個輕指的是輕便輕巧和簡潔易用,B2B2C 生鮮領域在線下是如此之重,那麼在交易場景線上化的過程當中,端的移動化就勢在必行,試想一下,讓菜市場攤位老闆人手一臺筆記本點開網頁選購支付,讓採購銷售抱着電腦去拜訪客戶,一邊聊蔬菜行情,一邊打開筆記本進行記錄,有沒有一種回到世紀初的感受。javascript

產品的移動化,這將是咱們展開這篇文章的背景,咱們會先了解小菜的產品託管在哪些端上,而後感覺這些端帶來的挑戰,最後是咱們聚焦如何作移動端的框架封裝,包括必要的基建部分。前端

小菜大前端的端有哪些

小菜早期圍繞着蔬菜銷地以客戶集單批發的模式摸爬滾打幾年,從上游的蔬菜供應商到下游批發市場的攤位老闆,在這個長長的鏈路中,咱們誕生了這樣幾款線上產品來服務於不一樣的人羣和場景,以前文章中也有介紹,這裏再彙總一下,共 9 款 App:java

  • 宋小菜 服務於銷地批發老闆的下單工具
  • 宋小福 服務於小菜內部銷售團隊的 CRM 銷售管理與客戶管理工具
  • 宋小倉 鏈接司機-物流-採購-銷售的蔬菜在途位置監控工具
  • 採祕    服務於小菜內部採購團隊的蔬菜品類採購工具
  • 麥大蔬 服務於上游蔬菜供應商的大宗農產品交易平臺
  • 宋大倉 服務於上游囤貨配資的進出庫管理平臺
  • 雲掌櫃 服務於產區加工廠進銷存的移動 Saas
  • 賣大蔬 服務於產銷行情與貨源泛用戶的內容小程序
  • 行情寶 服務於產銷兩地的內部行情采集和預測工具

前 7 款 App 都是基於 ReactNative 開發的 iOS/Android App,最後兩個是微信小程序,它們涵蓋了公司幾乎全部的協同場景和工做流。react

多端帶來的技術挑戰

1. 【物理現狀】移動端的碎片化

古典互聯網時代,由於要兼容 IE678 而痛苦不堪,Hack 黑魔法經驗基本表明前端水平,現在互聯網早已移動化,咱們理想中的移動端開發,看上去是能夠大膽使用新語法特性,只須要作好尺寸兼容就行了,但事實並不是如此,不只在移動端的瀏覽器不是如此,在移動端開發 RN App 也是如此,這是咱們某一款 App 一段時間內,所收集上來的手機廠商分佈:ios

能夠發現 Android 的碎片化很是嚴重,每個廠商下面有不一樣時期推出的不一樣型號的手機,這些手機有着不一樣版本的操做系統,不一樣的分辨率和用電策略,不一樣的後臺進程管理方式和用戶權限,要讓一款 App 在哪怕頭部 40% 的手機上兼容,都是一件艱難的事情,這個客觀物理現狀疊加下面的社區現狀,App 質量保證這件事情會變得雪上加霜。git

2. 【社區現狀】技術框架的不穩定性

回到本文的開頭,咱們在長鏈路的 B2B 生鮮場景中,爲了更快更輕,開發出了 7 款 App,並且未來隨着業務場景的拓展會誕生更多獨立 App 甚至是集大成的 App,因此技術選型不太可能選擇原生的 Java/Object-C 開發,尤爲對於創業公司,7 款 App 得須要多少名原生開發工程師才能搞定,高頻繁重的業務變化又怎樣靠堆人來保證?github

想清楚這些,一開始咱們就調研 ReactNative,並最終所有從原生切換到了 RN。經過跑過來的這 4 年來看,使用 RN 爲公司節約了大量的人力成本同時,也儘量的知足到了幾乎全部的須要快速迭代的業務場景,又快又輕,成爲宋小菜大前端團隊作事的一個典型特徵。面試

但換一個角度看,就是帶來的問題。又快又輕的背後是 RN 版本的飛速迭代,截止到目前,也就是 2019 年 4 月份,RN 尚未推出一個官方的正式的長期維護的穩定版本,什麼意思?就是 RN 目前依然處在不穩定的研發週期內,咱們依然站在刀尖上起舞,用不穩定的 RN 版本試圖開發穩定的應用。四年走來,咱們在 RN 的框架裏,多少次面對舊版本侷限性和新版本不穩定性都進退不得,舊版本的 Bug 可能會在新版本中修復,新版本引進則會帶來新版本本身的問題。npm

除了 RN 自身版本,還有第二個問題,圍繞着 RN 有不少業界優秀的組件,但這些社區組件甚至官方組件,都不必定能及時跟進最新的 RN 版本,同時還能兼容到較老的 RN 版本,因此 RN 升級致使的組件不兼容性,會引起你 Fork 修改組件的衝動,但這樣會帶來額外的開發成本和版本維護成本,取捨會成爲版本升降的終極問題。redux

在國內開發,還有第三個問題,就是中文文檔缺少,社區資源匱乏,參考文獻陳舊,可拿來主義的開源工程方案甚至社區線上線下會議分享都很缺少,一個不當心就會踩坑,這就是 RN 社區的現狀,咱們在刀尖浪花上獨步,App 選型背後的技術棧穩定性則成爲懸在頭上的一把鍘刀,你不知道何時會咔嚓一聲。

3. 【人才現狀】人員能力的長短不齊

咱們知道有一個詞叫作主觀能動性,表示沒有條件創造條件也能夠上。這個詞的主體就是人,聊完移動端設備現狀和社區現狀後,咱們來聊聊人的問題。RN 在國內真正開始普及使用,是從 2015 年開始,也就意味着,到 2019 年,一個 RN 工程師最多也就只有 4 年的工做經驗,而 RN 的 「Learn once, write anywhere」 也刺激着一切 Care 人員開支, Care 產品研發投入性價比的公司紛紛跳水研究 RN,爭搶 RN 人才,RN 是前端中的移動前端,前端有多搶手,那麼 RN 工程師就比它還要搶手。

這就致使基本上 RN 工程師很難靠外部招聘,只能靠內部培養。這也是小菜前端的成長曆程,咱們有  2 名資深 RN 工程師,一個是從服務端 Java,一個是從原生 Android 開發轉過來的。若是 RN 人手不足,產品支持的力度和速度就必定會遇到瓶頸,這就是咱們曾經面臨的問題,就是人才現狀,外招數量不足,內培速度有限,RN 工程師的數量和能力就時不時成爲公司業務擴張的瓶頸。

4. 【公司現狀】高密集業務的交付質量

做爲工程師,咱們有很強的自尊心和不容挑戰的代碼潔癖,但在一個創業公司裏面,甚至大公司的一個創業團隊裏面,咱們須要對接一些關鍵的業務節點,衝刺一些特定的時間窗口,而且要及時響應多變的業務,和業務背後多變的產品形態,這都會帶來很是密集的需求隊列。

這些密集的需求隊列對咱們的代碼質量有很是高的挑戰,一個組件用 5 分鐘思考如何抽象和用 50 分鐘思考,實現後的穩定性、兼容性都是不一樣的。如何保證產品定期交付上線,會是擺在咱們面前一個很是關鍵的命題,而這個難題以外,還有一個更難的命題等着咱們,那就是如何保證交付不延期的同時,還能保證交付質量。

要知道,若是一個項目代碼趕的太毛糙,後期維護起來的成本會是巨大的,甚至只能用更高的成本重構重寫。本質上,再次重構就必定是公司在爲早期的猛衝買單,爲這些技術債買單,如何不去買單或者如何用最小的成本買單,這跟咱們早期的業務密集程度,交付週期,質量把控有很大的關係。

綜上,移動端碎片化所帶來的兼容難度,RN 框架的侷限性,版本間差別帶來的不穩定性,技術社區資源的匱乏和前端團隊技術能力掣肘,再疊加上高密度的業務排期,讓前端開發這個原本很酷的事情,變得晴雨不定。

這些避不開的現實,是繞不過去的坎兒,必須經過人才儲備和技術基建來緩解,接下來咱們進入到本文的重點 - RN 框架的封裝。

RN 的 App 工程如何架構

RN 的 App 工程骨架,所有抽象完畢,再搭配上組件化,就能夠稱爲一個基於 ReactNative 定製的 App 框架了,而 RN 涉及到原生層面的技術細節太多,咱們暫不作討論,只專一在工程與業務的封裝上。

咱們在構建 RN App 工程時須要關注這幾個關鍵要素:

  • 配置管理
  • 靜態文件管理
  • 網絡請求
  • 組件管理
  • 路由管理
  • 數據緩存
  • App 的熱更新
  • 數據蒐集
  • 應用狀態管理

1. 配置管理

配置管理是指能夠靈活合理的管理 App 的內部環境,主要包括:

  • App 自己的一些配置
  • 所使用三方插件的配置

咱們在構建工程時儘可能將全部的配置抽象統一放置在一個地方,這樣便於查找和修改。可是因爲大多數配置都統一放在同一個地方,那麼就不免有部分文件要使用某個配置時其引用路徑比較長,好比:

import { pluginAConfig } from '../../../../../config'

這樣就形成了閱讀性不好且代碼不美觀,所以咱們可使用 Facebook 的 fbjs 模塊提供的一個功能 providesModule :

//config.js
/**
 * config for all
 * @providesModule config 
 * 使用 providesModule 將 config 暴露出去
 **/
import pluginAConfig from './plugin_a_config'

export default {
    pluginAConfig
}

// 而後在其餘文件中調用
// A.js
import { pluginAConfig } from 'config'

這樣就能很方便地在 App 的任意一處使用 config 了,可是咱們要避免濫用 providesMoudle ,由於使用了 providesMoudle 進行聲明的模塊的源碼,想要在編輯器中使用跳轉到定義的方式去查看比較困難,不利於團隊多人合做。

2. 靜態資源

靜態資源泛指會被屢次調用的圖片或 icon,咱們通常在 RN 使用圖片時是直接引用的:

import { Image } from 'react-native'

render(){
  return (
    <Image source={{uri: './logo.png'}} />
  )
}

當圖片須要在多處使用時,咱們可能會將這些可能會被反覆使用的圖片統一管理到 assets 文件夾中,統一管理和使用,可是當須要使用圖片資源的文件嵌套較深時,引用圖片就變得麻煩:

render(){
  return (
    <Image source={{uri: '../../../../assets/logo.png'}} />
  )
}

這個問題與配置管理的問題同樣,能夠首先將圖片資源按照類型進行分類,好比 assets 文件夾下有 button/icon/img/splash/svg 等,每個類型的結構以下:

- icon/
 - asset/
 - index.js

其中 asset 文件夾保存咱們的圖片資源,在 index.js 中對圖片進行引用並暴露爲模塊:

// index.js
export default {
   IconAlarmClockOrange: require('./asset/icon_alarm_clock_orange.png'),
   IconAvatarBlue: require('./asset/icon_avatar_blue.png'),
   IconArrowLeftBlue: require('./asset/icon_arrow_left_blue.png'),
   IconArrowUpGreen: require('./asset/icon_arrow_up_green.png')
}

而後再在 assets 文件夾下編輯 index.js ,將全部的圖片資源做爲 assets 模塊暴露出去,爲了不和其餘模塊衝突你能夠修改模塊名爲 xxAssets

// assets/index.js
/**
 * @providesModule myAssets
 **/
 import Splash from './splash'
 import Icon from './icon'
 import Img from './img'
 import Btn from './button'
 import Svg from './svg'

 export {
   Splash,
   Icon,
   Img,
   Btn,
   Svg
 }

// A.js
import { Icon } from 'myAssets'

render(){
  return (
    <Image source={Icon.IconAlarmClockOrange} />
  )
}

這樣,咱們就能很方便地將分散在項目各處的圖片資源統一到一個地方進行管理了,使用起來也很是方便。

3. 網絡請求

網絡請求這塊,react-native 使用 whatwg-fetch,咱們也能夠選擇其餘的三方包如 axios 來作網絡請求。但有時候咱們會在開發中遇到一個問題,那就是咱們明明已經在代碼裏已經修改了 cookie, 可是每次請求可能仍是會帶上以前的 cookie 從而形成一些困擾,因此這裏推薦一個實用的組件 Networking :

import { NativeModules } from 'react-native'
const { Networking } = NativeModules

// 手動清除已緩存 Cookie,這樣就能解決上述的問題了
Networking.clearCookies(callBack)

固然,Networking 的功能不止於此,還有不少其餘有趣的功能能夠發掘,能夠直接用它來包裝本身的網絡請求工具,還支持 abort ,能夠參考 源碼 來具體把玩。

4. 組件化

使用 RN 開發 App 自己效率就比較高,若是想要繼續進階就要考慮組件化開發,一旦涉及到組件化開發,就不可避免地會涉及到組件管理的問題,這裏的組件管理比較寬泛,它實際上應該指的是:

  • 組件規範
  • 組件類型劃分
  • 組件開發標準

組件規範指的是 UI 設計規範,咱們能夠與設計同窗交流規定好一套特定的規範,而後將通用的樣式屬性(如主題顏色,按鈕輪廓,返回按鍵,Tab 基礎樣式等)定義出來,便於全部的組件讓開發者在開發時使用,而不是開發者各自爲政在開發時重複寫樣式文件,這裏推薦一個比較好用的用於樣式定義的三方插件 react-native-extended-stylesheet ,咱們可使用這個插件定義咱們的通用屬性:

// mystyle
import { PixelRatio, Dimensions } from 'react-native'
import EStyleSheet from 'react-native-extended-stylesheet'

const { width, height } = Dimensions.get('window')

const globals = {
  /** build color **/
  $Primary: '#aa66ff',
  $Secondary: '#77aa33',
  $slimLine: 1 / PixelRatio.get(),
  /** dimensions **/
  $windowWidth: width,
  $windowHeight: height
}

EStyleSheet.build(globals)

module.exports = {
  ...EStyleSheet,
  create: styleObject => EStyleSheet.create(styleObject),
  build: (obj) => {
    if (!obj) {
      return
    }
    EStyleSheet.build(_.assign(obj, globals))
  }
}

// view.js
import MyStyleSheet from 'mystyle'

const s = MyStyleSheet.create({
  container: {
    backgroundColor: '$Secondary',
    width: '$windowWidth'
  }
})

render....

這樣,咱們就能在開發的任意插件或者 App 中直接使用這些基礎屬性,當某些屬性須要修改時只須要更新 mystyle 組件便可,另外還能夠衍生出主題切換等功能,使得開發更加靈活。

關於組件類型咱們會拋開三方組件以及原生組件,由於一旦涉及到這二者,須要寫的東西就太多了,咱們將組件按使用範圍分爲通用組件和業務組件兩大類。

首先什麼是業務組件?即咱們在開發某個業務產品經常使用到的組件,這個組件綁定了與業務相關的一些特殊屬性,除了這個業務開發之外,其餘地方都不適用,可是在開發這個業務時多個頁面會頻繁地使用到,因此咱們有必要將其抽象出來,方便使用。

什麼是通用組件?便可以在 App 範圍內使用甚至於跨 App 使用的組件,這裏能夠對這個類別進行細分,咱們將能跨 App 使用的組件上傳到了本身的搭建的私有 npm  倉庫,方便咱們的 App 開發者使用,同時,具備 App 本身特點的組件則放到工程中統一管理,一樣適用 providesModules 暴露出去。

制定一整套組件開發標準的是很重要的,由於不少組件開發多是多人維護的,有一套既定的規範就能夠下降維護成本,組件使用的說明文檔的完善也一樣重要。

5. 路由管理

開發 App 就不可避免地會遇到如何管理頁面以及處理頁面跳轉等問題,也就是路由管理問題,自從 Facebook 取消了 RN 自己自帶的 Navigator 之後,許多依賴於這個組件的開發者不得不將目光投向百花齊放的社區三方組件,FB 隨後推薦你們使用的是 react-community 推出的 react-navigation ,如今這個路由組件已經獨立出來了。咱們在開發時就是使用的這個組件做爲路由管理組件,只不過是在其基礎上作了一些定製 ,使得使用更加簡單,部分跳轉動做更加符合咱們的產品場景,推薦你們使用這個組件。固然,除去這個組件還有不少其餘的組件可供選擇:

路由管理做爲整個 App 的骨架,它是這幾個部分中最重要的一部分,合理地定製和使用路由管理能夠極大地簡化咱們的開發複雜度。

6. 數據緩存

通常狀況下須要緩存的數據基本上就多是咱們會在 App 不少地方都會使用到的全局數據,如用戶信息,App 設置(非應用層面的設置)等,RN 提供一個 AsyncStorage 存儲引擎,一般的使用方式是對這個數據引擎進行包裝後暴露出符合咱們要求的讀寫接口。這裏推薦另一種使用方式:

既然須要緩存的數據多是會在 App 不少地方使用到的全局數據,那麼咱們能夠將這些全局數據使用 redux 來進行管理,而利器 redux-persist 則能讓咱們很優雅地讀寫咱們的緩存數據。

同時,若是對 react-navigation 進行合理的定製,接管其路由管理,那麼咱們還能實現保存用戶退出 App 以前最後瀏覽的頁面的狀態,用戶在下次打開 App 依然能夠從以前瀏覽的地方繼續使用 App,固然,這個功能要謹慎使用!

7. 熱更新

App 的版本更新,RN 除了傳統的 App 更新外還有一個熱更新的可選項(傳統 App 更新也有熱更新,其原理就不太同樣了),社區大多數人都推薦使用 codepush 來進行熱更新,至於其後端解決方案 貌似已經有了一個 code-push-server ,咱們是使用本身的熱更新方案,其原理就是在不更新原生代碼的基礎上更新 JS 代碼和靜態資源文件。

8. 數據蒐集

蒐集的 App 使用數據(包括異常數據)並對此分析,根據分析來定位問題是保證 App 質量的有效手段之一。你能夠選擇本身搭建一套數據蒐集服務,包括客戶端 SDK 和服務端蒐集服務,或者選擇市場上已有的工具,目前較爲成熟的收據蒐集工具比較多,如友盟,mixpanel, countly 等等,在此不做贅述。

9. 應用狀態管理

React只是視圖層的解決方案,對於複雜應用,須要涉及狀態之間的共享、各層級組件之間的通訊、多接口之間調用的同步等等,就須要進行應用狀態管理,Facebook最先提出了Flux架構思想,後來社區又涌現了Redux、Mobx等不少種模式。通過調研比較,咱們選擇了Redux進行應用狀態管理,Redux的核心概念主要是經過Store、Action、Reducer、Dispatch實現單向數據流動,具體概念請參考官方文檔。Redux經過middleware機制,能夠對Redux進行各類能力加強,這個加強實際上是在action分發至任務處理reducer以前作一些額外的工做,dispatch發佈的action先依次傳遞給中間件,而後最終到達reducer,因此使用middleware機制咱們能夠拓展不少能力,例如咱們使用了狀態持久化插件redux-persist,狀態記錄和重播插件redux-logger,而異步操做插件咱們經歷了兩輪技術選型redux-thunk和redux-saga。

支持函數action的redux-thunk經過簡單的幾行代碼使得只處理plain object的action支持異步操做。

if (typeof action === 'function') {
    return action(dispatch, getState, extraArgument);
}
return next(action);

Redux-thunk的實現很是簡單,使用也很是靈活。咱們能夠在action中處理各類異步操做,也能夠作任何事情,可是它的缺點是它缺少對異步的直接處理,異步操做分散在各個action 中,而同步接口等操做依賴使用者本身的實現。

因而咱們進而選擇了支持generator的redux-saga。Redux-saga經過一個相似於獨立線程的方式管理你的應用程序中的反作用,這意味着你能夠經過普通的redux action開始、暫停或者取消saga線程。Redux-saga使用ES6的generator來管理異步流,使得業務邏輯的讀寫和測試變得更簡單。在咱們最新的架構中,咱們其實使用的是螞蟻金服開源的dva-core。之因此選用dva-core,主要是由於dva-core整合了redux和redux-saga,而且使開發者能夠經過一個命名的model文件集中管理一個業務邏輯的state,經過定義的effects管理反作用操做,經過定義reducers管理其餘處理函數。一個完整的model大概是這樣的:

export default {
    namespace: 'order',
  effects: {...},
  reducers: {...},
  subscription: {...}
}

最後,關於應用狀態管理,還有一個話題能夠討論,就是狀態的不可變性immutable。在redux中狀態是不可變的,每一個reducer都會產生新的不可變狀態。那麼這個不可變性是否須要不可變js庫(好比immutable.js)的支持呢?簡單來講,immutable.js能夠帶來計算效率和存儲效率的提高,可是它須要使用庫支持的數據類型,因此若是從頭構建一個應用,能夠選擇。若是是對於一個已有的複雜應用進行重構,那就須要綜合考慮一下了。

小結

總結一下,一個 RN App 架構應該要保證 App 的運行穩定以及開發的便捷。運行穩定這一方面,除了從 JS 層面(如單元測試,JS 錯誤上報等)保證以外,很大程度上還要依賴於原生層面的處理,因此團隊裏面要有同窗的精力能夠投在原生研究上面,至於開發便捷,咱們儘可能將複雜重要或者簡單繁瑣的操做在構建工程時就作掉,這樣也能夠大幅度提升咱們的開發效率,下降開發者之間的合做溝通成本。

:::info
Scott 近兩年不管是面試仍是線下線上的技術分享,遇到許許多多前端同窗,因爲團隊緣由,我的緣由,職業成長,技術方向,甚至家庭等等緣由,在理想國與現實之間,在放棄與堅守之間,搖擺不停,心酸硬扛,你們能夠找我聊聊南聊聊北,對工程師的宿命有更多的瞭解,有更多的看見與聽見,Scott 微信: codingdream,也能夠來 關注 Scott 語雀跟進最新動態,本文未經許可不準轉載,得到許可請聯繫 Scott,不然在公衆號上直接轉載,尤爲是裁剪內容後轉載,我都會直接進行投訴處理。
:::

2.png
1.png

相關文章
相關標籤/搜索