ReactNative-利用redux来规范化代码
本篇博文涉及到的知识点有ES6的相关语法、redux的使用、realm数据库,如果你对此不太了解,下面推荐几个链接学习:
对于reactnative(简称rn)初学者而言,可能更注重于如何快速搭建项目应用和快速写业务功能,而对于代码的质量和规范化可能没那么讲究,这就可能会导致项目越来越大的时候,维护成本加大,而且可扩展性不强。基于此,本博文讲的是一些分层思想,如何使用redux让你的rn代码看起来更规范化一点。
前言
一般一个rn应用会涉及到UI的界面展示、网络请求服务器数据、数据库等。但很多时候,我们没有把他们分开写,可能一个js文件就有涉及UI界面渲染,网络请求的相关代码或者是其他的一些操作,这就导致代码臃肿难维护。倘若把各个层的代码分开来写,让他们既能交互,又互不干扰,这样就很好维护,代码可读性也能得到改善。
如下图1所示,各个层的分工大概是这样:
- redux-数据流控制:在UI页面触发action,从而调起网络请求,数据通过reducer给到UI进行渲染
- UI层:只接收UI需要的数据,从reducer获取,不写其他无关代码
- 网络请求数据层:网络请求操作在redux的action中写
- 数据库层:将请求成功的数据存储到数据库
各个层要有自己的分工和定位,还要多封装一些模块出来,让代码更简化。
![](https://img.haomeiwen.com/i7535688/69f3c8207c5a4f52.png)
下面通过代码来演示以上说的写法。
一、UI篇
本篇主要是讲UI如何通过reducer获取数据,以一个DataTestView.js为例子。
-
DataTestView.js
在DataTestView.js中只引入一个ContainerView ,这个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,相当于一个容器,通过这个容器,我们能很好地利用redux来进行派发action操作和获取reducer数据给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}) 请求数据
(2) const {realityName, mobile, idNum, bankName} = this.props.data 获取数据进行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'}}>请求数据</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>{'手机:' + mobile}</Text>
<Text>{'身份证:' + idNum}</Text>
<Text>{'银行卡:' + bankName}</Text>
</View>
</ScrollView>
<LoadingView show={this.props.isFetching} text={'加载中...'}/>
</View>
)
}
}
-
MineDataActions.js
派发的相关action统一写在MineDataActions.js,这里涉及到以下一些主要的action:
1.开始发起请求的action
2.请求成功的action
3.请求失败的action
4.重置数据action
该js通过dispatch调起网络请求,然后根据请求成功或者失败派发了相关的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'
//开始发起请求action
export const requestPosts = () => ({
type: REQUEST_POST
})
//请求成功的action
export const reqSucc = (response) => ({
type: REQUEST_SUCCESS,
responseData: response
})
//请求失败的action
export const reqFail = () => ({
type: REQUEST_FAIL
})
//读取数据库的数据成功
export const getDBDataSucc = (dbData) => ({
type: GET_DB_SUCCESS,
dbData
})
//重置数据action
export const resetData = () => ({
type: RESET_DATA
})
//接口请求
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 + '')
})
}
//调用接口
const getMineData = async (reqParams) => {
let RQ = new MineRQ(reqParams)
const model = await RQ.requestData()
return model
}
//是否需要请求 当缓存的值是可用时可减少网络请求
const shouldFetchPosts = (state) => {
return true
}
export const fetchPostsIfNeed = reqParams => (dispatch, getState) => {
if (shouldFetchPosts(getState())) {
return dispatch(fetchPosts(reqParams))
}
}
- MineDataReducer.js
MineDataReducer.js主要是根据不同的action来改变数据state,数据state的改变会引起UI渲染。
而userData的数据中,isFetching表示是否请求中,这个参数用于请求中、请求成功、请求失败的一个状态标识,以便于在UI界面中是否显示loading加载框;data是请求成功后将数据保存至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)
二、请求数据篇
- BaseRQ.js
因为网络请求数据这块的代码大同小异,可以封装成一个基类BaseRQ,其他的RQ继承这个基类,这样省写很多代码,因为我这里还涉及到数据库相关操作,所以比较复杂。
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) {
//请求数据成功
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 {
//请求数据不成功
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就是接口请求类,继承于BaseRQ ,代码就只有不到十行,主要就是传opt和请求参数。所以,只要涉及大量的重复代码要考虑封装出一个基类。
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()
}
}
三、数据库篇
- BaseDao.js
跟网络请求BaseRQ类似,数据库的相关操作也可以封装出一个基类,主要是写数据库的增删改查等相关操作。
import realm from "../index";
export default class BaseDao {
constructor() {
}
//将服务器返回的数据保存到数据库对应的表
_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:
}
}
//删除数据库相关表的数据
_deleteTable = (dbName) => {
let Data = realm.objects(dbName)
try {
realm.write(() => {
realm.delete(Data)
})
} catch (error) {
console.log('error==' + error)
}
}
//查询数据库相关表的数据
_queryTableAll = (dbName) => {
let allData = realm.objects(dbName)
return allData
}
}
- MineDao.js
MineDao.js就是供外部使用的类,继承于BaseDao,主要是传要保存的数据和表名。
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 数据库相关表的设计,数据库相关数据的版本迁移等操作。
import Realm from 'realm';
/**
*测试表
*/
const Test = {
name: 'Test',
primaryKey: 'id',
properties: {
id: 'int',
username:'string',
password:'string'
}
}
/**
* 我的数据表
*/
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
关于realm数据库
移动端的数据存储推荐使用realm数据库,因为它被称为专为移动端而生的数据库,使用简单,高性能,支持reactnative,android和ios等,还有Realm Studio专门的调试工具。
这里不赘述它的使用,推荐一个realm使用教程,链接:Realm数据库在RN中的使用教程。
下面这张图是来源于官网:
![](https://img.haomeiwen.com/i7535688/c020f3d45c195027.png)
官方释义:
-
Realm Platform
Realm Platform是通过快速和高效的同步协议连接的基于NoSQL的服务器和客户端组件的组合,以支持实时、连接的应用程序和服务,这些应用和服务具有响应性且不受网络状态的影响。
Realm Platform有两个主要组件:Realm Database(领域数据库)和 Realm Object Server(领域对象服务器)。
-
Realm Database
Realm Database嵌入在客户端上,是一个功能齐全、面向对象、跨平台的数据库,可以在设备上本地保存数据。它可用于主要的移动语言,如SWIFT和Object-C(IOS)、Java(Android)、C#(Xamarin,.NET)和JavaScript(Reactinative和Node.js)。Realm Database是轻量级和高性能,能够处理非常大的数据负载和很快地运行查询。基于"live objects"(实时对象),它与Realm Database实时无缝地同步数据,无需编写网络、序列化或对象关系映射代码。这意味着您的应用程序将能够尽快刷新数据。由于数据库的“live objects”特性,它也是编写反应性应用程序的完美伴侣。
-
Realm Object Server
Realm的统一数据模型扩展到Realm Object Server,它反映设备上的Realm Database。它作为移动应用程序体系结构中的中间件组件,管理数据同步、事件处理和与遗留系统的集成。Realm Object Server可以在多个设备之间同时高效地同步数据,并自动解决冲突-所有冲突都是实时的。此外,它提供了一个单一的地方来管理所有通信,包括遗留API事务,否则可能会受到移动网络延迟和其他问题的影响。Realm Object Server有一个灵活的部署模型,可以在任何Kubernetes支持的环境中自我托管,无论是在预置环境中还是在云环境中,比如AWS、Azure、IBMCloud或GCP。此外,Realm Object Server也可以用在 Realm Cloud(领域云),或者在多云环境中。这种灵活性避免了锁定,并确保了数据所有权和数据移动性。
-
realm数据库到底是什么?它是如何实现数据同步更新?它跟其他数据库有什么区别?
看了官方的一点介绍,我也是有点云里雾里,后面找了两篇博文专门介绍了Realm Mobile Platform(简称RMP,realm移动端平台),对此做了以下几点总结:
- RMP,借助新的服务端技术,能够提供实时同步 (Realtime Synchronization)、冲突处理 (Conflict Resolution) 以及响应式事件处理 (Reactive Event Handling)
- Realm 是一个内核级别的嵌入式对象数据库,易用和高速是它的一大特点,开发者们不用去处理复杂的对象关系映射,他们只需要处理对象即可——也就是说,数据库即是数据模型。
- Realm 不是 ORM,也不基于 SQLite 创建,而是为移动开发者定制的全功能数据库。它可以将原生对象直接映射到Realm的数据库引擎中。
- Realm 是一个 MVCC 数据库 ,MVCC 指的是多版本并发控制,底层是用 C++ 编写的。其满足四大特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
- Realm 采用了zero-copy 架构,这样几乎就没有内存开销。这是因为每一个 Realm 对象直接通过一个本地 long 指针和底层数据库对应,这个指针是数据库中数据的钩子。
以上几点总结源于下面两篇博文,关于realm一些更加深入的原理解释,下面两篇博文写的很详细,推荐浏览了解。