在使用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 (
);
}
}
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一个标志位
在列表被选择时,需要使用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,很多原理还不是很清楚,写的也不是很好,只是把自己在开发过程中遇到的问题写出了,希望碰到同样问题的,能有点帮助。