ReactNative-利用redux來規(guī)范化代碼

本篇博文涉及到的知識(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è)層要有自己的分工和定位,還要多封裝一些模塊出來,讓代碼更簡化。

圖1.png

下面通過代碼來演示以上說的寫法。

一、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.png

官方釋義

  • 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ì),推薦瀏覽了解。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容