React-Redux 之store, action, reducer以及combineReducers(reducers)

在使用react做项目开发的时候,避免不了使用到redux,关于redux在这里不做详细赘述,大概的概念性的东西action,reducer,store最基本的三个元素都应该是清楚地,并清楚的知道他们是干嘛的。

在我开发的项目中使用到redux,我就说一下怎么在react中集成redux,以及拆分多个reducer,来实现state管理。

这是官方给出的redux的工作流,打个比方说,我们去图书馆借书

React Component就是图书借阅者,

action就相当于于我们的图书管理员或者借书机器,

store就是图书馆或者说是图书储存室,里面存储的都是书籍

我们去借书首先会发起action咨询图书管理员,或者直接找图书管理员来确认书籍书否可借阅(比如被借走,损坏修复中等),

图书管理员会去图书室查询一下书籍是否可借阅,如果可借阅,那么他就会将书籍返给你,如果不行,那么也会通知你。

这就是一个简单的redux流程。就相当于一次借阅图书的过程。

好了,知道怎么个流程,就来看看怎么定义的。

我们知道,通过脚手架或者其他方式创建react项目,都会有个入口文件,我们要向使用redux,就必须在入口文件中使用我们定义的store

import React, { Component } from 'react';
import { BrowserRouter, browserHistory } from 'react-router-dom'
import { Provider } from 'react-redux'
import hisroty from './history/History'
import Layouts from '../src/layouts/Layouts'
import store from './stores/store'
import './App.css';
class App extends Component {
  constructor(props) {
    super(props);
    this.state = {}
  }
  render() {
    return (
      <Provider store={store}>
        <BrowserRouter hisroty={hisroty} >
          <Layouts />
        </BrowserRouter>
      </Provider>

    );
  }
}

export default App;

store的定义,官方给出了使用createStore来创建一个store,官方推荐只有一个store文件,那么我们在src下创建一个store文件夹,并创建一个store.js文件,定义如下:

import { createStore } from 'redux'  //  引入createStore方法
import appReducer from '../reducers/reducer.js'
const store = createStore(
    appReducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()) // 创建数据存储仓库
export default store   // 暴露出去

由于我们的项目都是大型的或者说是多应用多模块的应用,那么我们希望分块处理不同模块,既然store不支持多文件,我们可以创建多个reducer,然后将多个reducer合并到一个中,然后引入到store.js中

我们看一下appReducer怎么定义的

import {combineReducers} from 'redux';
 
import nasClientsReducer from './nasClientsReducer';
import loginReducer from './loginReducer';
 
const appReducer = combineReducers({
    nasClientsReducer,
    loginReducer
});
export default appReducer;

这里就是使用到了combineReducers,使用这个东西有个地方很蛋疼,就是返回的store,并不是我们希望的格式,我们希望即使使用combineReducers,应该返回一个state的状态树,类似这种格式的:

{
    list: [],
    columns: [],
    editFlag: true,
    deleteFlag: true,
    pagination: {
        pageSize: 5,
        total: 0
    },
    loading: false,
    visible: false,//详情页
    isShowModal: false,//删除模态框
    selectedSize: 0
}

这样就会导致一个问题就是,所有的数据都会挂载到一个state上,导致数据混乱。

而官方返回的标准格式是这样的(我们在模块中添加打印语句):

constructor(props) {
        console.log("store.getState()",store.getState())
        super(props);
        //获取state
        this.state = store.getState().nasClientsReducer
        //订阅redux的状态
        store.subscribe(this.storeChange)
    }

可以看到他返回的是两个reducer,这样可以做到个模块之间的数据隔离,因为在SPA应用中,各个模块直接大多是独立的,这样做是没问题的,那我们在取state里面的数据时就需要注意,我们只能使用下面的方式(我看了看网上的文章,确实有重命名的,有兴趣的可以搜索一下看看):

this.state = store.getState().nasClientsReducer

那么我么现在定义好了store和总的reducer,来看一下nasClientsReducer怎么定义的:

nasClientsReducer.js

import actionTypes from '../actions/nas-clients/actionTypes';
const defaultState = {
    list: [],
    columns: [],
    editFlag: true,
    deleteFlag: true,
    pagination: {
        pageSize: 5,
        total: 0
    },
    loading: false,
    visible: false,//详情页
    isShowModal: false,//删除模态框
    selectedSize: 0
}
export default (state = defaultState, action) => {
    console.log("发布")
    console.log(actionTypes)
    console.log(action)
    if (action.type === actionTypes.DELETE_DISABLED) {
        let newState = JSON.parse(JSON.stringify(state)) //深度拷贝state
        newState.deleteFlag = action.value
        return newState;
    }
    if (action.type === actionTypes.EDIT_DISABLED) {
        let newState = JSON.parse(JSON.stringify(state)) //深度拷贝state
        newState.editFlag = action.value
        return newState;
    }
    if (action.type === actionTypes.DELETE_SHOW) {
        let newState = JSON.parse(JSON.stringify(state)) //深度拷贝state
        newState.isShowModal = action.value
        return newState;
    }
    if (action.type === actionTypes.DETAIL_SHOW) {
        let newState = JSON.parse(JSON.stringify(state)) //深度拷贝state
        newState.isShowModal = action.value
        return newState;
    }
    if (action.type === actionTypes.LOADING_SHOW) {
        let newState = JSON.parse(JSON.stringify(state)) //深度拷贝state
        newState.isShowModal = action.value
        return newState;
    }
    if (action.type === actionTypes.LIST_DATA) {
        let newState = JSON.parse(JSON.stringify(state)) //深度拷贝state
        newState.list = action.value
        console.log(newState)
        return newState;
    }
    if (action.type === actionTypes.COLUMN) {
        let newState = JSON.parse(JSON.stringify(state)) //深度拷贝state
        newState.columns = action.value
        return newState;
    }
    if (action.type === actionTypes.SELECTED_ROWS) {
        let newState = JSON.parse(JSON.stringify(state)) //深度拷贝state
        newState.selectedSize = action.value
        return newState;
    }
    return state
}

很简单的定义,在reducer中,我们不能去修改state中的值,只能重新copy之后,修改新的state值,然后return。

由于在模块中会频繁使用常量即action type所以我就定义在一个单独的文件中取名actionTypes.js。

actionTypes.js

const actionTypes = {
    DELETE_DISABLED: "DELETE_DISABLED",
    EDIT_DISABLED: "EDIT_DISABLED",
    DELETE_SHOW: "DELETE_SHOW",
    DETAIL_SHOW: "DETAIL_SHOW",
    LOADING_SHOW: "LOADING_SHOW",
    LIST_DATA: "LIST_DATA",
    COLUMN: "COLUMN",
    SELECTED_SIZE: "SELECTED_SIZE"
}
export default actionTypes

为了方便简化代码,我会额外创建一个文件,取名actionCreator来创建我们需要的action,那么在我们在模块中使用action的时候就可以使用creator去创建action

actionCreator.js:

import actionTypes from './actionTypes';
export const deleteDisbaled = (value) => ({
    type: actionTypes.DELETE_DISABLED,
    value
})
export const editDisbaled = (value) => ({
    type: actionTypes.EDIT_DISABLED,
    value
})
export const deleteShow = (value) => ({
    type: actionTypes.DELETE_SHOW,
    value
})
export const detailShow = (value) => ({
    type: actionTypes.DETAIL_SHOW,
    value
})
export const loadingShow = (value) => ({
    type: actionTypes.LOADING_SHOW,
    value
})
export const listData = (value) => ({
    type: actionTypes.LIST_DATA,
    value
})
export const createColumn = (value) => ({
    type: actionTypes.COLUMN,
    value
})
export const selectedSize = (value) => ({
    type: actionTypes.SELECTED_SIZE,
    value
})

现在所有的准备工作都做完了,开始基于store,action, reducer来实现我们真正的模块。

现在要做的是,我需要将dataview中的数据也放到了store中,不适用redux时,我们一般会将页面用到的数据通过state在constructor中进行初始化,那么换成redux之后,我们怎么去处理呢?

当加载我们的业务组件时,在render函数渲染之后,一般会在componentDidMount中取server端请求数据,然后再绑定到state上,就可以更新组件。

在这之前我们还是需要在constructor中取初始化我们的state,就是通过store去获取初始化定义的数据:

this.state = store.getState().nasClientsReducer 对,不再是store.getState()了,而是store.getState().nasClientsReducer。

constructor(props) {
        console.log("store.getState()",store.getState())
        super(props);
        //获取state
        this.state = store.getState().nasClientsReducer
        //订阅redux的状态
        store.subscribe(this.storeChange)
    }

这样获取的数据是没有dataview数据的,我们需要在componentDidMount中通过store.dispatch发布一下:数据

//组件渲染成功后,调用接口去后台请求数据
    componentDidMount() {
        this.getData();
    }

    //在这里请求后台服务并设置list,page
    getData = () => {
        this.setState({ loading: true });
        // axios.get('/api/ham/radius/client/getClientList')
        //     .then(function (response) {
        //         console.log(response);
        //     })
        //     .catch(function (error) {
        //         console.log(error);
        //     });

        const list = [
            {
                "id": 1, "startNASIp": "1.0.0.1", "endNASIp": "255.255.255.255",
                "nasName": "All Managed Devices", "description": "All Devices Managed By OV",
                "shareSecurity": "123456", "dm_attributes": ["User-Name", "Calling-Station-Id"]
            },
            {
                "id": 2, "startNASIp": "2.0.0.1", "endNASIp": "255.255.255.255",
                "nasName": "All Managed Devices", "description": "All Devices Managed By OV",
                "shareSecurity": "123456", "dm_attributes": ["User-Name", "Calling-Station-Id"]
            },
            {
                "id": 3, "startNASIp": "3.0.0.1", "endNASIp": "255.255.255.255",
                "nasName": "All Managed Devices", "description": "All Devices Managed By OV",
                "shareSecurity": "123456", "dm_attributes": ["User-Name", "Calling-Station-Id"]
            },
            {
                "id": 4, "startNASIp": "4.0.0.1", "endNASIp": "255.255.255.255",
                "nasName": "All Managed Devices", "description": "All Devices Managed By OV",
                "shareSecurity": "123456", "dm_attributes": ["User-Name", "Calling-Station-Id"]
            },
            {
                "id": 5, "startNASIp": "5.0.0.1", "endNASIp": "255.255.255.255",
                "nasName": "All Managed Devices", "description": "All Devices Managed By OV",
                "shareSecurity": "123456", "dm_attributes": ["User-Name", "Calling-Station-Id"]
            },
            {
                "id": 6, "startNASIp": "6.0.0.1", "endNASIp": "255.255.255.255",
                "nasName": "All Managed Devices", "description": "All Devices Managed By OV",
                "shareSecurity": "123456", "dm_attributes": ["User-Name", "Calling-Station-Id"]
            }
        ]
        
        store.dispatch(listData(list))
        store.dispatch(createColumn(columns))
    }

这里listData和createColumn都是在actionCreator中创建的action。

发布之后我们可以打印一下,action:

光是发布时不够的,数据并没有成功渲染,那我们知道肯定是state没有更新

因为你虽然通过dispatch发布了,也返回了新的state,但是组件是不知道的,我们需要使用store的另外一个方法store.subscribe去订阅redux的状态。

constructor(props) {
        console.log("store.getState()",store.getState())
        super(props);
        //获取state
        this.state = store.getState().nasClientsReducer
        //订阅redux的状态
        store.subscribe(this.storeChange)
    }
    storeChange = () => {
        this.setState(store.getState().nasClientsReducer)
        console.log(this.state)
    }

我们再看一下页面:

数据被更新了。

再来看个简单的,当我进入到dataview页面时,delete和edit按钮应该是disabled的,所以我们需要在table被选择的时候,去发布一下。

我实在button上绑定了state中的editFlag一个标志位

<Button type="primary" disabled={this.state.editFlag}>
     <Link to={{ path: '/policy-engine/nas-client/add', state: { item: this.state.item, model: "edit" } }}>
          <Icon type="edit"></Icon>
      </Link>
 </Button>

在列表被选择时,需要使用dispatch来发布

onSelect: (record, selected, selectedRows) => {
                selectedRows.length === 1 ?
                    store.dispatch(editDisbaled(false)) : store.dispatch(editDisbaled(true))
                selectedRows.length >= 1 ?
                    store.dispatch(deleteDisbaled(false)) : store.dispatch(deleteDisbaled(true))

                store.dispatch(selectedSize(selectedRows.length))
                console.log(this.state);
            },

这样就可以实现,当选中一条数据时,edit是可以点击的,不选中时是不能点击的:

刚研究redux,很多原理还不是很清楚,写的也不是很好,只是把自己在开发过程中遇到的问题写出了,希望碰到同样问题的,能有点帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值