React Native App應用架構設計
在上一篇介紹了React Native開發環境搭建,我們已經可以在本地成功執行一個helloword應用了,本節將開始詳細分析如何搭建一個React Native App應用架構,並支援完整本地執行預覽。
前言
現在已經有很多腳手架工具,如ignite,支援一鍵建立一個React Native App專案結構,很方便,但是享受方便的同時,也失去了對專案架構及技術棧完整學習的機會,而且通常腳手架建立的應用技術架構並不能完全滿足我們的業務需求,需要我們自己修改,完善,所以如果希望對專案架構有更深掌控,最好還是從0到1理解一個專案。
專案結構與技術棧
首先使用react-native-cli
工具建立一個React Native應用:
react-native init fuc
生成專案結構如下圖:
- andorid和ios目錄分別存放對應原生平臺程式碼;
package.json
為專案依賴管理檔案;index.ios.js
為ios平臺入口檔案,index.android.js
為android平臺入口檔案,通常用來註冊React Native App根元件;.babelrc
檔案,babel的配置檔案,React Native預設使用babel編譯JavaScript程式碼;__tests__
專案測試目錄。
我們看到並沒有存放React Native原生JavaScript程式碼的目錄,這需要我們自己進行建立了,通常建立一個src
目錄作為App應用Javascript部分所有程式碼和資源的根目錄,一個src/constants
src/config
目錄儲存全域性配置,一個src/helpers
存放全域性輔助,工具類方法,一個src/app.js
作為RN部分入口檔案,另外通常還需要建立儲存各模組redux的目錄,redux中介軟體的目錄等。技術棧
專案架構搭建很大部分依賴於專案的技術棧,所以先對整個技術棧進行分析,總結:
- react native + react庫是專案前提
- App應用導航(不同於React應用的路由概念)
- 應用狀態管理容器
- 是否需要Immutable資料
- 應用狀態的持久化
- 非同步任務管理
- 測試及輔助工具或函式
- 開發除錯工具
根據以上劃分決定選用以下第三方庫和工具構成專案的完整技術棧:
- react-native + react類庫;
- react-navigation管理應用導航;
- redux作為JavaScript狀態容器,react-redux將React Native應用與redux連線;
- Immutable.js支援Immutable化狀態,redux-immutable使整個redux store狀態樹Immutable化;
- 使用redux-persist支援redux狀態樹的持久化,並新增redux-persist-immutable拓展以支援Immutable化狀態樹的持久化;
- 使用redux-saga管理應用內的非同步任務,如網路請求,非同步讀取本地資料等;
- 使用jest整合應用測試,使用lodash,ramda等可選輔助類,工具類庫;
- 使用reactotron除錯工具
針對以上分析,完善後的專案結構如圖:
如上圖,在專案根目錄下建立src
目錄,而在src目錄中依次建立12個目錄與1個React Native部分入口js檔案。
開發除錯工具
React Native App開發目前已經有諸多除錯工具,常用的如atom和Nuclide,移動端模擬器自帶的除錯工具,Reactron等。
Nuclide
Nuclide是由Facebook提供的基於atom的整合開發環境,可用於編寫、執行和 除錯React Native應用。
模擬器除錯工具
在模擬器啟動執行App後,瀏覽器會自動開啟 http://localhost:8081/debugger-ui
頁,可以在控制檯進行js除錯輸出及遠端js斷點除錯;在模擬器終端使用快捷鍵command
加D
鍵即可開啟除錯工具,包括重新載入應用,開啟熱載入,切換DOM審視器等:
Reactotron
Reactotron是一款跨平臺除錯React及React Native應用的桌面應用,能動態實時監測並輸出React應用等redux,action,saga非同步請求等資訊,如圖:
首先初始化Reactotron相關配置:
import Config from './DebugConfig';
import Immutable from 'immutable';
import Reactotron from 'reactotron-react-native';
import { reactotronRedux as reduxPlugin } from 'reactotron-redux';
import sagaPlugin from 'reactotron-redux-saga';
if (Config.useReactotron) {
// refer to https://github.com/infinitered/reactotron for more options!
Reactotron
.configure({ name: 'Os App' })
.useReactNative()
.use(reduxPlugin({ onRestore: Immutable }))
.use(sagaPlugin())
.connect();
// Let's clear Reactotron on every time we load the app
Reactotron.clear();
// Totally hacky, but this allows you to not both importing reactotron-react-native
// on every file. This is just DEV mode, so no big deal.
console.tron = Reactotron;
}
然後啟使用console.tron.overlay
方法拓展入口元件:
import './config/ReactotronConfig';
import DebugConfig from './config/DebugConfig';
class App extends Component {
render () {
return (
<Provider store={store}>
<AppContainer />
</Provider>
)
}
}
// allow reactotron overlay for fast design in dev mode
export default DebugConfig.useReactotron
? console.tron.overlay(App)
: App
至此就可以使用Reactotron客戶端捕獲應用中發起的所有的redux和action了。
元件劃分
React Native應用依然遵循React元件化開發原則,元件負責渲染UI,元件不同狀態對應不同UI,通常遵循以下元件設計思路:
- 佈局元件:僅僅涉及應用UI介面結構的元件,不涉及任何業務邏輯,資料請求及操作;
- 容器元件:負責獲取資料,處理業務邏輯,通常在render()函式內返回展示型元件;
- 展示型元件:負責應用的介面UI展示;
- UI元件:指抽象出的可重用的UI獨立元件,通常是無狀態元件;
展示型元件 | 容器元件 | |
---|---|---|
目標 | UI展示 (HTML結構和樣式) | 業務邏輯(獲取資料,更新狀態) |
感知Redux | 無 | 有 |
資料來源 | props | 訂閱Redux store |
變更資料 | 呼叫props傳遞的回撥函式 | Dispatch Redux actions |
可重用 | 獨立性強 | 業務耦合度高 |
跨平臺適應
建立跨平臺應用時,雖然React Native做了大量跨平臺相容的工作,但是依然存在一些需要為不同平臺開發不同程式碼的情況,這時候需要額外處理。
跨平臺目錄
我們可以將不同平臺程式碼檔案以不同目錄區分開來,如:
/common/components/
/android/components/
/ios/components/
common
目錄下存放公用檔案,android
目錄存放android檔案程式碼,ios
存放ios檔案程式碼,但是通常都選擇React Native提供的更好方式,後文介紹。
Platform模組
React Native內建了一個Platform模組,用以區分應用當前執行平臺,當執行在ios平臺下時,Platform.OS
值為ios
,執行在android平臺下則為android
,可以利用此模組載入對應平臺檔案:
var StatusBar = Platform.select({
ios: () => require('ios/components/StatusBar'),
android: () => require('android/components/StatusBar'),
})();
然後正常使用該StatusBar元件即可。
React Native平臺檢測
當引用某元件時,React Native會檢測該檔案是否存在.android
或.ios
字尾,如果存在則根據當前平臺載入對應檔案元件,如:
StatusBar.ios.js
StatusBar.indroid.js
同一目錄下存在以上兩個檔案,則可以使用以下方式引用:
import StatusBar from './components/StatusBar';
React將會根據當前平臺載入對應字尾檔案,推薦使用此方式做平臺元件級程式碼適配,而對於區域性小部分需要適配平臺的程式碼可以使用Platform.OS
值,如下,若僅僅需要在ios平臺下新增一個更高的margin-top值且不是公用樣式時:
var styles = StyleSheet.create({
marginTop: (Platform.OS === 'ios') ? 20 : 10,
});
App應用導航與路由
不同於React應用的單頁面路由,React Native通常都是多頁面形式存在,以導航方式在不同頁面和元件間切換,而不是路由方式控制不同元件展示,最常使用的是react-navigation導航庫。
導航和路由
在React web應用中,頁面UI元件展示和切換完全由路由控制,每一個路由都有對應的URL及路由資訊,在React Native應用則不是由路由驅動元件展示,而是由導航控制切換屏展示,每一屏有各自的路由資訊。
或許你已經依賴react-router的單頁面應用路由配置方式,希望建立一個Url驅動的跨平臺App應用,託福於活躍的的開源社群,你可以使用react-router-native,但是並不推薦,因為對於App而言,從互動和體驗考慮,還是更適合使用多頁面(屏)形式。
react-navigation
使用react-navigation可以定義跨平臺的應用導航結構,也支援配置渲染跨平臺的導航欄,tab欄等元件。
內建導航模組
react-navigation提供以下幾個方法支援建立不同的導航型別:
- StackNavigator:建立導航屏棧(stack),所有屏(screen)以棧的方式存在,一次渲染一屏,在切換屏時提高變換動畫,當開啟某一屏時,將該屏放置在棧頂;
- TabNavigator:建立一個Tab式導航,渲染一個Tab選單欄,使使用者可以切換不同屏;
- DrawerNavigator:建立抽屜式導航,從屏的左邊滑出一屏;
STACKNAVIGATOR
StackNavigator支援跨平臺以變換方式切換不同屏,並且將當前屏放置在棧頂,呼叫方式如下:
StackNavigator(RouteConfigs, StackNavigatorConfig)
RouteConfigs
導航棧路由(route)配置物件,定義route名和route物件,該route物件定義當前路由對應的展示元件,如:
// routes為路由資訊物件
StackNavigator({
[routes.Main.name]: Main,
[routes.Login.name]: {
path: routes.Login.path,
screen: LoginContainer,
title: routes.Login.title
}
}
如上,表明當應用導航至路由routes.Login.name
時,渲染LoginContainer
元件,