本篇博文涉及到的知識(shí)點(diǎn)有ES6的相關(guān)語法、redux的使用、realm數(shù)據(jù)庫,如果你對(duì)此不太了解,下面推薦幾個(gè)鏈接學(xué)習(xí):
對(duì)于reactnative(簡稱rn)初學(xué)者而言,可能更注重于如何快速搭建項(xiàng)目應(yīng)用和快速寫業(yè)務(wù)功能,而對(duì)于代碼的質(zhì)量和規(guī)范化可能沒那么講究,這就可能會(huì)導(dǎo)致項(xiàng)目越來越大的時(shí)候,維護(hù)成本加大,而且可擴(kuò)展性不強(qiáng)?;诖耍静┪闹v的是一些分層思想,如何使用redux讓你的rn代碼看起來更規(guī)范化一點(diǎn)。
前言
一般一個(gè)rn應(yīng)用會(huì)涉及到UI的界面展示、網(wǎng)絡(luò)請(qǐng)求服務(wù)器數(shù)據(jù)、數(shù)據(jù)庫等。但很多時(shí)候,我們沒有把他們分開寫,可能一個(gè)js文件就有涉及UI界面渲染,網(wǎng)絡(luò)請(qǐng)求的相關(guān)代碼或者是其他的一些操作,這就導(dǎo)致代碼臃腫難維護(hù)。倘若把各個(gè)層的代碼分開來寫,讓他們既能交互,又互不干擾,這樣就很好維護(hù),代碼可讀性也能得到改善。
如下圖1所示,各個(gè)層的分工大概是這樣:
- redux-數(shù)據(jù)流控制:在UI頁面觸發(fā)action,從而調(diào)起網(wǎng)絡(luò)請(qǐng)求,數(shù)據(jù)通過reducer給到UI進(jìn)行渲染
- UI層:只接收UI需要的數(shù)據(jù),從reducer獲取,不寫其他無關(guān)代碼
- 網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)層:網(wǎng)絡(luò)請(qǐng)求操作在redux的action中寫
- 數(shù)據(jù)庫層:將請(qǐng)求成功的數(shù)據(jù)存儲(chǔ)到數(shù)據(jù)庫
各個(gè)層要有自己的分工和定位,還要多封裝一些模塊出來,讓代碼更簡化。

下面通過代碼來演示以上說的寫法。
一、UI篇
本篇主要是講UI如何通過reducer獲取數(shù)據(jù),以一個(gè)DataTestView.js為例子。
-
DataTestView.js
在DataTestView.js中只引入一個(gè)ContainerView ,這個(gè)ContainerView是什么呢?有什么作用?
import ContainerView from './container/MineDataContainerView'
export default class DataTestView extends Component {
constructor() {
super(...arguments)
}
render() {
return (
<View style={styles.root}>
<ContainerView {...this.props}/>
</View>
)
}
}
-
MineDataContainerView.js
MineDataContainerView.js就是ContainerView,相當(dāng)于一個(gè)容器,通過這個(gè)容器,我們能很好地利用redux來進(jìn)行派發(fā)action操作和獲取reducer數(shù)據(jù)給UI使用。
而MineDataUI才是真正展示給用戶看的UI頁面。
import {connect} from 'react-redux'
import MineDataUI from "../MineDataUI";
import {fetchPostsIfNeed} from "../action/MineDataActions";
const mapStateToProps = (state,props) => {
return{
data:state.mineData.data,
isFetching:state.mineData.isFetching
}
}
const mapDispatchToProps = dispatch => ({
requestData: (reqParam) => dispatch(fetchPostsIfNeed(reqParam))
})
export default connect(mapStateToProps, mapDispatchToProps)(MineDataUI)
-
MineDataUI.js
該UI頁面只做兩件事:
(1) this.props.requestData({name, psd}) 請(qǐng)求數(shù)據(jù)
(2) const {realityName, mobile, idNum, bankName} = this.props.data 獲取數(shù)據(jù)進(jìn)行UI渲染
export default class MineDataUI extends Component {
constructor() {
super(...arguments)
}
componentDidMount(){
this._getData()
}
_getData = () => {
const name = 'hozan'
const psd = 5201314
this.props.requestData({name, psd})
}
_gotoList = () => {
InteractionManager.runAfterInteractions(() => {
this.props.navigation.navigate('listtestview')
})
}
render() {
const {realityName, mobile, idNum, bankName} = this.props.data
return (
<View style={styles.container}>
<View style={{flexDirection: 'row', marginTop: 20, marginBottom: 10}}>
<TouchableOpacity onPress={this._getData} style={styles.btn}>
<View>
<Text style={{color: 'white'}}>請(qǐng)求數(shù)據(jù)</Text>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={this._gotoList} style={styles.btn}>
<View>
<Text style={{color: 'white'}}>跳去列表</Text>
</View>
</TouchableOpacity>
</View>
<ScrollView>
<View style={{marginHorizontal: 10,}}>
<Text>{'姓名:' + realityName}</Text>
<Text>{'手機(jī):' + mobile}</Text>
<Text>{'身份證:' + idNum}</Text>
<Text>{'銀行卡:' + bankName}</Text>
</View>
</ScrollView>
<LoadingView show={this.props.isFetching} text={'加載中...'}/>
</View>
)
}
}
-
MineDataActions.js
派發(fā)的相關(guān)action統(tǒng)一寫在MineDataActions.js,這里涉及到以下一些主要的action:
1.開始發(fā)起請(qǐng)求的action
2.請(qǐng)求成功的action
3.請(qǐng)求失敗的action
4.重置數(shù)據(jù)action
該js通過dispatch調(diào)起網(wǎng)絡(luò)請(qǐng)求,然后根據(jù)請(qǐng)求成功或者失敗派發(fā)了相關(guān)的action。
export const REQUEST_POST = 'REQUEST_POSTS'
export const REQUEST_SUCCESS = 'REQUEST_SUCCESS'
export const GET_DB_SUCCESS = 'GET_DB_SUCCESS'
export const REQUEST_FAIL = 'REQUEST_FAIL'
export const RESET_DATA = 'RESET_DATA'
//開始發(fā)起請(qǐng)求action
export const requestPosts = () => ({
type: REQUEST_POST
})
//請(qǐng)求成功的action
export const reqSucc = (response) => ({
type: REQUEST_SUCCESS,
responseData: response
})
//請(qǐng)求失敗的action
export const reqFail = () => ({
type: REQUEST_FAIL
})
//讀取數(shù)據(jù)庫的數(shù)據(jù)成功
export const getDBDataSucc = (dbData) => ({
type: GET_DB_SUCCESS,
dbData
})
//重置數(shù)據(jù)action
export const resetData = () => ({
type: RESET_DATA
})
//接口請(qǐng)求
const fetchPosts = (reqParams) => dispatch => {
dispatch(requestPosts())
return getMineData(reqParams)
.then((model) => {
if (model.result) {
dispatch(getDBDataSucc(model.data))
}
return model.next()
})
.then((model) => {
if (model.result) {
dispatch(reqSucc(model.data))
}
EDMoney.Toast.show(model.data.msg)
})
.catch((error) => {
dispatch(reqFail())
EDMoney.Toast.show(error + '')
})
}
//調(diào)用接口
const getMineData = async (reqParams) => {
let RQ = new MineRQ(reqParams)
const model = await RQ.requestData()
return model
}
//是否需要請(qǐng)求 當(dāng)緩存的值是可用時(shí)可減少網(wǎng)絡(luò)請(qǐng)求
const shouldFetchPosts = (state) => {
return true
}
export const fetchPostsIfNeed = reqParams => (dispatch, getState) => {
if (shouldFetchPosts(getState())) {
return dispatch(fetchPosts(reqParams))
}
}
- MineDataReducer.js
MineDataReducer.js主要是根據(jù)不同的action來改變數(shù)據(jù)state,數(shù)據(jù)state的改變會(huì)引起UI渲染。
而userData的數(shù)據(jù)中,isFetching表示是否請(qǐng)求中,這個(gè)參數(shù)用于請(qǐng)求中、請(qǐng)求成功、請(qǐng)求失敗的一個(gè)狀態(tài)標(biāo)識(shí),以便于在UI界面中是否顯示loading加載框;data是請(qǐng)求成功后將數(shù)據(jù)保存至data。
import {
handleActions
} from 'redux-actions'
import {Record} from "immutable";
import {
REQUEST_POST,
REQUEST_SUCCESS,
GET_DB_SUCCESS,
REQUEST_FAIL,
RESET_DATA
} from '../action/MineDataActions'
const userData = Record({
isFetching: false,
data: {},
}, 'userData')
const initState = new userData()
export default handleActions({
[REQUEST_POST]: (state, action) => state.set('isFetching', true),
[REQUEST_SUCCESS]: (state, action) => state.set('data', action.responseData)
.set('isFetching', false),
[GET_DB_SUCCESS]:(state,action)=>state.set('data',action.dbData),
[REQUEST_FAIL]: (state, action) => state.set('isFetching', false),
[RESET_DATA]: (state, action) => initState
}, initState)
二、請(qǐng)求數(shù)據(jù)篇
- BaseRQ.js
因?yàn)榫W(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)這塊的代碼大同小異,可以封裝成一個(gè)基類BaseRQ,其他的RQ繼承這個(gè)基類,這樣省寫很多代碼,因?yàn)槲疫@里還涉及到數(shù)據(jù)庫相關(guān)操作,所以比較復(fù)雜。
import FetchData from "../net/FetchData";
import MineDao from "../db/DAO/MineDao";
import UserListDao from "../db/DAO/UserListDao";
export default class BaseRQ {
constructor(opt, reqParam) {
this.opt = opt
this.reqParam = reqParam
}
request = () => {
let nextStep = async () => {
let res
res = await FetchData.fetchDataWithPost(this.opt, this.reqParam)
if (res && res.error == 1) {
//請(qǐng)求數(shù)據(jù)成功
let totalInfo = Object.assign({}, {...res, requesttime})
switch (this.opt) {
case '110':
let mineDao = new MineDao()
mineDao.insertTable({...totalInfo, name: this.reqParam.name})
break;
case '111':
let userListDao = new UserListDao()
userListDao.insertTable(totalInfo)
break;
default:
}
return {
data: totalInfo,
result: true,
}
} else {
//請(qǐng)求數(shù)據(jù)不成功
return {
data: res,
result: false
}
}
}
let dbData
switch (this.opt) {
case '110':
let mineDao = new MineDao()
dbData = mineDao.queryTableAll()
if (dbData && dbData.length > 0) {
return {
data: JSON.parse(dbData[0].data),
next: nextStep,
result: true,
};
} else {
return {
data: {},
next: nextStep,
result: false
}
}
break;
case '111':
let userListDao = new UserListDao()
dbData = userListDao.queryTableAll()
if (dbData && dbData.length > 0) {
let data = []
dbData.forEach((item) => {
data.push(JSON.parse(item.data))
})
return {
data: data,
next: nextStep,
result: true,
}
} else {
return {
data: [],
next: nextStep,
result: false
}
}
break;
default:
}
}
}
- MineRQ.js
MineRQ.js就是接口請(qǐng)求類,繼承于BaseRQ ,代碼就只有不到十行,主要就是傳opt和請(qǐng)求參數(shù)。所以,只要涉及大量的重復(fù)代碼要考慮封裝出一個(gè)基類。
import BaseRQ from "./BaseRQ";
import {Test1Opt} from "../net/OptConfig";
import MineReqParam from "../model/requestparams/MineReqParam";
export default class MineRQ extends BaseRQ {
constructor(reqParam: MineReqParam) {
super(Test1Opt, {...reqParam})
}
requestData = () => {
return this.request()
}
}
三、數(shù)據(jù)庫篇
- BaseDao.js
跟網(wǎng)絡(luò)請(qǐng)求BaseRQ類似,數(shù)據(jù)庫的相關(guān)操作也可以封裝出一個(gè)基類,主要是寫數(shù)據(jù)庫的增刪改查等相關(guān)操作。
import realm from "../index";
export default class BaseDao {
constructor() {
}
//將服務(wù)器返回的數(shù)據(jù)保存到數(shù)據(jù)庫對(duì)應(yīng)的表
_insertTable = (data, dbName) => {
switch (dbName) {
case 'Mine':
let {requesttime, name} = data
realm.write(() => {
let allData = realm.objects(dbName).filtered(`accName="${name}"`)
if (allData && allData.length > 0) {
allData[0].data = JSON.stringify(data)
allData[0].requesttime = requesttime
} else {
realm.create(dbName, {
accName: data.accName,
data: JSON.stringify(data),
requesttime: requesttime
})
}
})
break
case 'UserList':
let {userList} = data
realm.write(() => {
let allData = realm.objects(dbName)
realm.delete(allData)
userList.forEach((item) => {
realm.create(dbName, {
id: item.id,
data: JSON.stringify(item),
requesttime: data.requesttime
})
})
})
break
default:
}
}
//刪除數(shù)據(jù)庫相關(guān)表的數(shù)據(jù)
_deleteTable = (dbName) => {
let Data = realm.objects(dbName)
try {
realm.write(() => {
realm.delete(Data)
})
} catch (error) {
console.log('error==' + error)
}
}
//查詢數(shù)據(jù)庫相關(guān)表的數(shù)據(jù)
_queryTableAll = (dbName) => {
let allData = realm.objects(dbName)
return allData
}
}
- MineDao.js
MineDao.js就是供外部使用的類,繼承于BaseDao,主要是傳要保存的數(shù)據(jù)和表名。
import BaseDao from "./BaseDao";
export default class MineDao extends BaseDao {
constructor() {
super()
}
insertTable = (data) => {
this._insertTable(data,'Mine')
}
deleteTable = () => {
this._deleteTable('Mine')
}
queryTableAll = () => {
return this._queryTableAll('Mine')
}
}
- realm— index.js
realm 數(shù)據(jù)庫相關(guān)表的設(shè)計(jì),數(shù)據(jù)庫相關(guān)數(shù)據(jù)的版本遷移等操作。
import Realm from 'realm';
/**
*測試表
*/
const Test = {
name: 'Test',
primaryKey: 'id',
properties: {
id: 'int',
username:'string',
password:'string'
}
}
/**
* 我的數(shù)據(jù)表
*/
const Mine={
name:'Mine',
properties:{
accName:'string',
data:'string',
}
}
let realm=new Realm({
schema:[Test,Mine],
schemaVersion:1,
migration:(oldRealm, newRealm)=>{
}
})
export const clearCache = () => {
realm.write(() => {
realm.deleteAll()
})
}
export default realm
關(guān)于realm數(shù)據(jù)庫
移動(dòng)端的數(shù)據(jù)存儲(chǔ)推薦使用realm數(shù)據(jù)庫,因?yàn)樗环Q為專為移動(dòng)端而生的數(shù)據(jù)庫,使用簡單,高性能,支持reactnative,android和ios等,還有Realm Studio專門的調(diào)試工具。
這里不贅述它的使用,推薦一個(gè)realm使用教程,鏈接:Realm數(shù)據(jù)庫在RN中的使用教程。
下面這張圖是來源于官網(wǎng):

官方釋義:
-
Realm Platform
Realm Platform是通過快速和高效的同步協(xié)議連接的基于NoSQL的服務(wù)器和客戶端組件的組合,以支持實(shí)時(shí)、連接的應(yīng)用程序和服務(wù),這些應(yīng)用和服務(wù)具有響應(yīng)性且不受網(wǎng)絡(luò)狀態(tài)的影響。
Realm Platform有兩個(gè)主要組件:Realm Database(領(lǐng)域數(shù)據(jù)庫)和 Realm Object Server(領(lǐng)域?qū)ο蠓?wù)器)。
-
Realm Database
Realm Database嵌入在客戶端上,是一個(gè)功能齊全、面向?qū)ο?、跨平臺(tái)的數(shù)據(jù)庫,可以在設(shè)備上本地保存數(shù)據(jù)。它可用于主要的移動(dòng)語言,如SWIFT和Object-C(IOS)、Java(Android)、C#(Xamarin,.NET)和JavaScript(Reactinative和Node.js)。Realm Database是輕量級(jí)和高性能,能夠處理非常大的數(shù)據(jù)負(fù)載和很快地運(yùn)行查詢?;?live objects"(實(shí)時(shí)對(duì)象),它與Realm Database實(shí)時(shí)無縫地同步數(shù)據(jù),無需編寫網(wǎng)絡(luò)、序列化或?qū)ο箨P(guān)系映射代碼。這意味著您的應(yīng)用程序?qū)⒛軌虮M快刷新數(shù)據(jù)。由于數(shù)據(jù)庫的“l(fā)ive objects”特性,它也是編寫反應(yīng)性應(yīng)用程序的完美伴侶。
-
Realm Object Server
Realm的統(tǒng)一數(shù)據(jù)模型擴(kuò)展到Realm Object Server,它反映設(shè)備上的Realm Database。它作為移動(dòng)應(yīng)用程序體系結(jié)構(gòu)中的中間件組件,管理數(shù)據(jù)同步、事件處理和與遺留系統(tǒng)的集成。Realm Object Server可以在多個(gè)設(shè)備之間同時(shí)高效地同步數(shù)據(jù),并自動(dòng)解決沖突-所有沖突都是實(shí)時(shí)的。此外,它提供了一個(gè)單一的地方來管理所有通信,包括遺留API事務(wù),否則可能會(huì)受到移動(dòng)網(wǎng)絡(luò)延遲和其他問題的影響。Realm Object Server有一個(gè)靈活的部署模型,可以在任何Kubernetes支持的環(huán)境中自我托管,無論是在預(yù)置環(huán)境中還是在云環(huán)境中,比如AWS、Azure、IBMCloud或GCP。此外,Realm Object Server也可以用在 Realm Cloud(領(lǐng)域云),或者在多云環(huán)境中。這種靈活性避免了鎖定,并確保了數(shù)據(jù)所有權(quán)和數(shù)據(jù)移動(dòng)性。
-
realm數(shù)據(jù)庫到底是什么?它是如何實(shí)現(xiàn)數(shù)據(jù)同步更新?它跟其他數(shù)據(jù)庫有什么區(qū)別?
看了官方的一點(diǎn)介紹,我也是有點(diǎn)云里霧里,后面找了兩篇博文專門介紹了Realm Mobile Platform(簡稱RMP,realm移動(dòng)端平臺(tái)),對(duì)此做了以下幾點(diǎn)總結(jié):
- RMP,借助新的服務(wù)端技術(shù),能夠提供實(shí)時(shí)同步 (Realtime Synchronization)、沖突處理 (Conflict Resolution) 以及響應(yīng)式事件處理 (Reactive Event Handling)
- Realm 是一個(gè)內(nèi)核級(jí)別的嵌入式對(duì)象數(shù)據(jù)庫,易用和高速是它的一大特點(diǎn),開發(fā)者們不用去處理復(fù)雜的對(duì)象關(guān)系映射,他們只需要處理對(duì)象即可——也就是說,數(shù)據(jù)庫即是數(shù)據(jù)模型。
- Realm 不是 ORM,也不基于 SQLite 創(chuàng)建,而是為移動(dòng)開發(fā)者定制的全功能數(shù)據(jù)庫。它可以將原生對(duì)象直接映射到Realm的數(shù)據(jù)庫引擎中。
- Realm 是一個(gè) MVCC 數(shù)據(jù)庫 ,MVCC 指的是多版本并發(fā)控制,底層是用 C++ 編寫的。其滿足四大特性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)
- Realm 采用了zero-copy 架構(gòu),這樣幾乎就沒有內(nèi)存開銷。這是因?yàn)槊恳粋€(gè) Realm 對(duì)象直接通過一個(gè)本地 long 指針和底層數(shù)據(jù)庫對(duì)應(yīng),這個(gè)指針是數(shù)據(jù)庫中數(shù)據(jù)的鉤子。
以上幾點(diǎn)總結(jié)源于下面兩篇博文,關(guān)于realm一些更加深入的原理解釋,下面兩篇博文寫的很詳細(xì),推薦瀏覽了解。