美文网首页React Native开发
ReactNative-利用redux来规范化代码

ReactNative-利用redux来规范化代码

作者: Hozan | 来源:发表于2018-12-03 18:02 被阅读21次

本篇博文涉及到的知识点有ES6的相关语法、redux的使用、realm数据库,如果你对此不太了解,下面推荐几个链接学习:

对于reactnative(简称rn)初学者而言,可能更注重于如何快速搭建项目应用和快速写业务功能,而对于代码的质量和规范化可能没那么讲究,这就可能会导致项目越来越大的时候,维护成本加大,而且可扩展性不强。基于此,本博文讲的是一些分层思想,如何使用redux让你的rn代码看起来更规范化一点。

前言

一般一个rn应用会涉及到UI的界面展示、网络请求服务器数据、数据库等。但很多时候,我们没有把他们分开写,可能一个js文件就有涉及UI界面渲染,网络请求的相关代码或者是其他的一些操作,这就导致代码臃肿难维护。倘若把各个层的代码分开来写,让他们既能交互,又互不干扰,这样就很好维护,代码可读性也能得到改善。
如下图1所示,各个层的分工大概是这样:

  • redux-数据流控制:在UI页面触发action,从而调起网络请求,数据通过reducer给到UI进行渲染
  • UI层:只接收UI需要的数据,从reducer获取,不写其他无关代码
  • 网络请求数据层:网络请求操作在redux的action中写
  • 数据库层:将请求成功的数据存储到数据库

各个层要有自己的分工和定位,还要多封装一些模块出来,让代码更简化。

图1.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中的使用教程
下面这张图是来源于官网:

realm.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一些更加深入的原理解释,下面两篇博文写的很详细,推荐浏览了解。

相关文章

  • ReactNative-利用redux来规范化代码

    本篇博文涉及到的知识点有ES6的相关语法、redux的使用、realm数据库,如果你对此不太了解,下面推荐几个链接...

  • Redux 与表单

    1、对于简单的表单应用 ①使用redux-form-utils工具库来减少重复冗余的代码,它能利用高阶组件的特性为...

  • 一起学react(4) 史上最详细react-redux 源码分

    今天来分析一下react-redux源码react-redux所有代码注释地址:https://github.co...

  • Redux 实践

    为了更深刻理解redux,我们将使用一个简化的购物车项目来学习Redux。 组织Redux代码 首先,在src文件...

  • Redux及相关类库使用详解

    本篇文章涉及代码地址 前言 本文是作者对redux相关使用的总结,利用一个简单的计数器应用介绍来flux、redu...

  • 简易Redux库的实现

    基础Redux实现 Redux的强大之处也由于它的简洁,我们甚至可以用几行代码来实现一个简易的Redux生态。 R...

  • React - 组件通信

    一.利用props/函数传值 father.js child.js 二.利用Redux进行通信 redux/ind...

  • Git代码提交规范

    一、代码提交规范化的目的 为了部门提交代码信息格式规范化 为了更好的追溯代码、筛选 为了更加快速的定位提交代码所涉...

  • react-redux

    React-Redux是基于Redux的一个封装,他简化了组件和redux代码的耦合度,使页面代码看起来更加的清爽...

  • redux 基本操作

    今天主要使redux的基本操作(这里不是react-redux) 这里主要用代码来叙述 一个小的计数器 通过如下三...

网友评论

    本文标题:ReactNative-利用redux来规范化代码

    本文链接:https://www.haomeiwen.com/subject/jinhcqtx.html