在react项目中使用redux进行数据管理

1,101 阅读10分钟

前言

在我上一篇文章中,介绍了如何搭建一个简单的react项目,里面包含路由跳转, 组件按需加载,及一部分个人理解上的项目文件构成,这篇文章将继续为大家 在上一篇文章的基础上进行一个redux数据管理的功能实现及对代码的分析研究.当前,整个redux构建流程只是基于个人对redux的研究理解及使用,如果有不同意见欢迎共同探讨学习. 附上上一篇的链接 juejin.cn/post/684490…

关于redux

首先,在讲操作之前,按照大家的惯例,先介绍一下redux,这里我就直接复制粘贴官网链接了. www.redux.org.cn/

操作前基础步骤

首先依然是安装依赖

  • 安装redux依赖, react-redux依赖, 以及 redux-thunk依赖
npm i  redux react-redux redux-thunk
  • 安装针对数据操作的依赖
npm i immutable prop-types

安装完依赖之后,就可以在上一篇文章的基础上,进行redux数据管理了.

首先依然是在src目录下创建一个store文件夹,用于存放数据源文件, 然后在store目录下创建一个store.js文件,将store变量暴露出来,给index.js使用.

这里分为三个步骤, 首先是创建文件夹与文件,然后先在index.js中添加用于添加数据管理的标签Provider.

说明下Provider标签的作用: Provider的作用

Provider是作为整个App的容器,在你原有的App Container的基础上再包上一层 接受Redux的store作为props,并将其声明为context的属性之一 子组件可以在声明了contextTypes之后可以方便的通过this.context.store访问到store.
附上一个链接 www.jianshu.com/p/186956ac6…

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from './store/store';

ReactDOM.render(
    // Provider标签用于声明使用redux数据存储包含的元素
    <Provider store={store}>
        <Router>
            <Route component= { App } />
        </Router>
    </Provider>
    , document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

第二步,我们来把index.js中引入的store补全一下,

import { createStore, applyMiddleware, combineReducers } from 'redux'
import thunk from 'redux-thunk'

import * as pro from './Collecte/reducer'

let store =  createStore (
    combineReducers({...pro}),
    applyMiddleware(thunk)
)

export default store;

这里说明一下, 从redux里引入的三个部分可能比较不容易记, 作者提供一个简单的方法,就是在store.js导入的时候,可以先写出从redux导入

import {} from 'redux'

然后,只需要记住, c c a ,三个部分的首拼,会自动提示出来, 这个小技巧页适合任何其他的文件导入.

接下我们创建一个数据管理文件, 在store当中,每一个页面级组件的数据都可以创建一个文件夹存储对应的数据操作文件.

上面我们有这样一句话,

import * as pro from './Collecte/reducer'

这个的意思是从当前store目录下的Collecte文件夹里的reducer文件中导入所有暴露内容作为pro对象.

好了, 解释完了我们就来创建用于管理Collect页面的数据文件. 这里还要说明一下, 在使用redux管理数据时, 每个页面级组件数据文件夹下都创建三个文件, 一个action-type, action 以及reducer.

个人理解这也是redux的一种基本格式吧.下面针对三个文件,分别做下个人的理解 首先:

  • action-type.js文件 : 声明操作数据类型,便于reducer.js中switch条件语句作逻辑判断.

  • action.js 文件作用: 声明获取操作数据的方法, 各方法中设置方法调用的形参,与返回的action数据类型对象,

    action对象中应包含操作类型: 即在action-Type中声明的操作类型; 如果是获取数据源信息的方法 需包含dataList对象用于存储获取到的state初始数据. 如果是操作数据的方法, 则需包含便于在数据源信息中查询到需要修改元素的标识,如index, id等等(具备唯一性) 简而言之, 如果是操作数据的方法, 返回action对象中应包含操作数据类型及形参.

  • redux.js用于创建针对action数据变化所进行的一些逻辑操作,简单来说就是用来提供改变数据的逻辑.

好了,理解了每个文件的作用,那么接下来,我们来依次补全这三个文件.

首先是action-type.js, 这个文件夹,我们需要声明的是数据操作类型.

这里,我们声明四个常量接收四个字符串, 都大写是为了防止出错, 分别的意思就是英文翻译: 获取商品, 切换选中, 添加商品, 清除商品.

定好四个操作类型后,在redux.js中,当我们对每个操作类型定义业务逻辑的时候,就可以用switch-case条件语句来对每个类型进行添加业务逻辑了.

export const GETPRODUCTION = 'GETPRODUCTION'

export const TOGGLESELECT = 'TOGGLESELECT'

export const EDITPRODUCTION = 'EDITPRODUCTION'

export const CLEARSELECTED = 'CLEARSELECTED'

好了,补全了action-type文件,接下来我们补全下action文件.

这里总结几个需要注意的地方:

  • 首先

  • 第二个,action文件定义操作数据的方法, 需要用到几个方法,就定义几个,每个方法里不需要写逻辑,因为reducer文件会针对这些方法作业务逻辑,然后, 声明方法中,需要定义业务逻辑需要使用的形参, 方法返回的是一个action集合,里面包含判断操作类型的type,和做业务逻辑需要用到的形参.

  • 第三个,以当前为例,如果数据源是通过后台请求得到的,那么就再getProData中使用async和await异步转同步,将返回数据赋值给dataList.

API是一个统一的文件,存放当前项目所使用的业务网络请求,下一篇会整理一些比较常规的项目结构以及代码规范的问题.

import * as pro from './action-type'

// 当组件调用获取数据方法时,返回记录此时的数据信息作为初始化数据类型.可以在此处通过async await 
//异步转同步方法请求后台地址获取参数,并将结果数据项作为dataList参数值传递给state的dataList属性.

export const getProData = () => {
    return {
        type: pro.GETPRODUCTION, //给返回的对象中添加type属性的作用是在reducer文件中,可以通过type属性来判断是什么数据操作类型
        dataList: [ {selectNum: 1}, {selectNum: 2}, {selectNum: 3} ]
    }
}
//修改state数据属性方法,声明形参,并返回action对象集合, 在reducer文件中,
//通过获取action对象来操作dataList修改存储对应的值类型对象.
export const togSelectPro = index => {
    return {
        type: pro.TOGGLESELECT,
        index
    }
}

export const editPro = (index, selectNum) => {
    return {
        type: pro.EDITPRODUCTION,
        index,
        selectNum
    }
}
export const clearSelect = () => {
    return {
        type: pro.CLEARSELECTED
    }
}

好了,补全了action文件, 接下来我们补全下用于处理业务逻辑的reducer文件

import * as pro from './action-type'
// 自测Immutable的方法意义, 类似后台java语言方法.
import Immutable from 'immutable';

let defaultState = {
    dataList: []
}

// action: 对应调用action方法时,每个方法返回的对象.
export const proData = (state = defaultState, action) => {
    let immuDataList; // 用于存储操作数据源
    let immuItem; // 用于存储当前需要操作的数据
    // 使用switch-case条件语句来进行业务逻辑区分
    switch (action.type) {
        // 如果操作类型是获取Data
        case  pro.GETPRODUCTION:
            return {...state, ...action}
        //如果操作类型是切换选中
        case pro.TOGGLESELECT:
            //首先使用List方法 获取到操作数据源
            immuDataList = Immutable.List(state.dataList)
            //然后使用Map方法,选择出需要操作的元素
            immuItem = Immutable.Map(state.dataList[action.index])
            //然后使用set方法,改变选中元素的选中状态, 用get方法获取当前状态再取反,达到切换效果
            immuItem = immuItem.set('selectStatus', !immuItem.get('selectStatus'))
            //然后把修改的数据写入到数据源中,进行替换
            immuDataList = immuDataList.set(action.index, immuItem)
            // 完成后, 再将新的数据源返回出来
            return {...state, ...{dataList: immuDataList.toJS()}}
        // 如果操作类型是添加商品
        case pro.EDITPRODUCTION: 
            //同样先使用List方法,获取操作数据源
            immuDataList = Immutable.List(state.dataList)
            //然后使用Map方法,选择出需要操作的元素
            immuItem = Immutable.Map(state.dataList[action.index])
            // 然后使用set方法,修改操作元素的值
            immuItem = immuItem.set('selectNum', action.selectNum)
             //然后把修改的数据写入到数据源中,进行替换
             immuDataList = immuDataList.set(action.index, immuItem)
             // 完成后, 再将新的数据源返回出来
             return {...state, ...{dataList: immuDataList.toJS()}}
        //如果操作类型是清除商品
        case pro.CLEARSELECTED:
             // 获取state数据中的数组集合
             immuDataList = Immutable.fromJS (state.dataList);
             //遍历数组集合,通过set方法设置每个元素的值 状态
             for (let i = 0; i < state.dataList.length; i ++) {
                 immuDataList = immuDataList.update ( i, item => {
                     item = item.set('selectStatus', false);
                     item = item.set ('selectNum', 0);
                     return item;
                 })
             }
             //返回写入数据的新集合对象
             return {...state, ...{dataList: immuDataList.toJS()}};
        default: 
             return state; 
    }
}

以上,我们对于一个页面级数据源的操作逻辑就设置完成了, 接下来就将它应用到页面组件中.

这里以Collecte页面组件为例. 首先需要创建连接, 这里需要注意的是,一个文件中只能同时有一个exprot default

import React, { Component } from 'react';
import {Header} from '../../components'
// 组件与数据源建立链接
import { connect } from 'react-redux';
// 对数据进行深层判断是否改变
import { is, fromJS } from 'immutable';
// prop-types用于设定数据类型,防止产生不必要的误差,如果已有数据类型与设定数据类型不一致,则会提示报错.
import PropTypes from 'prop-types';
import { getProData, togSelectPro, editPro } from '../../store/Collecte/action'

class Collecte extends Component {
    static propTypes = {
        // 设定proData必须是object集合类型
        proData: PropTypes.object.isRequired,
        // 设定以下三个属性值必须是函数类型.
        getProData: PropTypes.func.isRequired,
        togSelectPro: PropTypes.func.isRequired,
        editPro: PropTypes.func.isRequired
    }
    shouldComponentUpdate (nextProps, nextState) {
        return !is(fromJS ( this.props), fromJS (nextProps)) || !is(fromJS (this.state), fromJS (nextState))
    }

    componentDidMount () {
        if (!this.props.proData.dataList.length) {
            let a = this.props.getProData();
            console.log(a)
        }
    }

    handleEdit = (index, num) => {
        let currentNum = this.props.proData.dataList[index].selectNum + num;
        if (currentNum < 0 ) {
            return
        }
        this.props.editPro (index, currentNum); 
    }
    render () {
        return (
            <div>
                <Header/>
                收集页
                {
                    this.props.proData.dataList.map((item, index) => {
                        return <div className="pro-item-edit" key={index}>
                                <span onClick={this.handleEdit.bind(this, index, -1)}> - </span>
                                <span className="pro-num"> 当前数字为: {item.selectNum} </span>
                                <span onClick={this.handleEdit.bind(this, index, 1)}> + </span>
                               </div>
                    })
                }
            </div>
        )
    }
}

// 建立连接,声明数据信息及操作方法. 以及自调用对象(当前需要与数据源建立连接的类对象)
export default connect (state => (
    { proData: state.proData }
), {
    getProData,
    togSelectPro,
    editPro
})(Collecte)

当前页面级组件与store建立连接,具体作用,我都写在了注释里.那么到这一步,在react中使用redux的步骤及各部分代码的作用解析就完成了.

在当前窗口下,切换路由并不会影响collecte所改变的数据值,而且也可以与其他页面共享数据.具体操作就简单的复制粘贴一下,我就拿Detail这页面级组件作为观察响应数据变化,

import React, { Component } from 'react';
import {Header} from '../../components';
import { connect } from 'react-redux';
// 对数据进行深层判断是否改变
import { is, fromJS } from 'immutable';
// prop-types用于设定数据类型,防止产生不必要的误差,如果已有数据类型与设定数据类型不一致,则会提示报错.
import PropTypes from 'prop-types';
// 导入数据操作方法
import { getProData, togSelectPro, editPro } from '../../store/Collecte/action'

class Detail extends Component {
    static propTypes = {
        // 设定proData必须是object集合类型
        proData: PropTypes.object.isRequired,
        // 设定以下三个属性值必须是函数类型.
        getProData: PropTypes.func.isRequired,
        togSelectPro: PropTypes.func.isRequired,
        editPro: PropTypes.func.isRequired
   }
     // 判断数据是否改变, 如果相同返回false, 提升性能优化.
     shouldComponentUpdate (nextProps, nextState) {
        return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
    }

    componentDidMount () {
        if (!this.props.proData.dataList.length) {
            this.props.getProData()
        }
    }
    render () {
        return (
            <div>
                <Header/>
                详情页
                {
                    this.props.proData.dataList.map((item, index) => {
                        return <div className="pro-item-edit" key={index}>
                                <span className="pro-num"> 当前数字为: {item.selectNum} </span>
                               </div>
                    })
                }
            </div>
        )
    }
}

export default connect(state => (
    {proData: state.proData}
), {
    getProData,
    togSelectPro,
    editPro
})(Detail)

这边,加上与Collecte组件相同的返回hTML结构,可以形成双向响应, 但我这里就简单的填一下,看官们可以自己实测一下.

好了,有关于在react项目中使用redux进行数据管理就讲到这里, 如果有不同的见解,欢迎共同探讨学习.