redux中间件及其处理机制
redux中文网API文档之redux中间件:https://cn.redux.js.org/api/applymiddleware
redux中间件作用示意图:
中间件使用:
redux-logger:
import { createStore, applyMiddleware } from 'redux';
import reducer from './reducers';
import reduxLogger from 'redux-logger'
const store = createStore(
reducer,
applyMiddleware(reduxLogger)
);
export default store;
redux-thunk/redux-promise语法及原理:
延迟函数:返回promise示例,在指定的时间后,才会让实例为成功
const delay = (interval = 1000) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, interval)
})
}
let fn = async () => {
await delay()
return { x: 200 }
}
//把fn()执行,立即拿到的结果并不是{ x: 200 ],而是一个promise实例
console.log(fn());// Promise {<pending>}
fn().then(value => {
console.log(value);
})
如果直接将延迟函数应用于行为对象派发中,是有问题的:
const voteAction = {
async support() {
await delay()
return {
type: TYPES.VOTE_SUP
};
},
...
}
原因:
在不使用任何中间件的情况下,actionCreator对象中,是不支持异步操作的;我们要保证方法执行,要必须立即返回标准的action对象,类似于下述这种:
support() {
return {
type: TYPES.VOTE_SUP
};
},
但真实项目中,往往我们是真的需要异步操作的,例如在派发的时候,我们需要先向服务器发送请求,把数据拿到后再进行派发。此时我们需要依托于一些中间件来进行处理。
官方推荐:redux-thunk
const voteAction = {
//redux-thunk中间件的语法
support() {
return async (dispatch) => {
await delay()
dispatch({
type: TYPES.VOTE_SUP
});
}
},
...
}
原理:
发现问题:
原理改进理解:
也可以基于redux-promise中间件,完成异步的派发:
const voteAction = {
...
//redux-promise中间件的语法
async oppose() {
await delay(2000)
return {
type: TYPES.VOTE_OPP
};
}
};
原理:
完整的操作代码:
store文件夹下的index.js:
import { createStore, applyMiddleware } from 'redux';
import reducer from './reducers';
import reduxLogger from 'redux-logger'
import reduxThunk from 'redux-thunk'
import reduxPromise from 'redux-promise'
const store = createStore(
reducer,
applyMiddleware(reduxLogger, reduxThunk, reduxPromise)
);
export default store;
VoteAction.js:
import * as TYPES from '../action-types';
//延迟函数:返回promise实例,在指定的时间后,才会让实例成功
const delay = (interval = 1000) => {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, interval)
})
}
const voteAction = {
//redux-thunk中间件的语法
support() {
return async (dispatch) => {
await delay()
dispatch({
type: TYPES.VOTE_SUP
});
}
},
//redux-promise中间件的语法
async oppose() {
await delay(2000)
return {
type: TYPES.VOTE_OPP
};
}
};
export default voteAction;
总结:redux-promise和redux-thunk中间件,都是处理异步派发的
他们相同的地方:都是派发两次
- 第一次派发用的都是重写后的dispatch,这个方法不会去校验对象是否有type属性,也不会在乎传递的对象是否为标准普通对象
- 此次派发,仅仅是为了第二次派发做准备
- redux-thunk:把返回的函数执行,把真正的dispath传递给函数
- redux-promise:监听返回的promise实例,在实例为成功后,需要基于真正的disptch把成功的结果再进行派发
区别:
redux-thunk的第二次派发是手动处理的;而redux-promise的第二次派发是自动处理的。
基于redux重构TASK OA
redux在真实项目中的运用:
- 实现组件之间的信息共享或者通信
- 对数据进行缓存/存储 => 减少向服务器发送请求的次数
redux工程化
store/action-types.js:
export const TASK_LIST = 'TASK_LIST'
export const TASK_REMOVE = 'TASK_REMOVE'
export const TASK_UPDATE = 'TASK_UPDATE'
获取当前时间:
new Date().toLocaleString(‘zh-CN’, { hour12: false })
store/reducers/taskReducer.js:
import * as TYPES from '../action-types'
import _ from '../../assets/utils'
const initial = {
taskList: null
}
export default function taskReducer(state = initial, action) {
state = _.clone(true, state)
let { taskList } = state
switch (action.types) {
case TYPES.TASK_LIST:
state.taskList = action.list
break
case TYPES.TASK_DELETE:
if (Array.isArray(taskList)) {
state.taskList = taskList.filter(item => {
return +item.id !== +action.id
})
}
break
case TYPES.TASK_UPDATE:
if (Array.isArray(taskList)) {
state.taskList = taskList.map(item => {
if (+item.id === +action.id) {
item.state = 2
item.complete = new Date().toLocaleString('zh-CN', { hour12: false })
}
return item
})
}
break
default:
}
return state
}
// 获取全部任务 => dispath({type:'TASK_LIST',list:[...]})
// 删除任务 => dispath({type:'TASK_DELETE',id:xxx})
// 完成任务 => dispath({type:'TASK_UPDATE',id:xxx})
store/reducers/index.js:
import { combineReducers } from "redux";
import taskReducer from "./taskReducer";
const reducer = combineReducers({
task: taskReducer
})
export default reducer
store/actions/taskAction.js:
import * as TYPES from '../action-types'
import { getTaskList } from '../../api'
const taskAction = {
// 异步派发:从服务器获取全部任务,同步到redux容器中
async queryAllList() {
let list = []
try {
let result = await getTaskList()
if (+result.code === 0) {
list = result.list
}
} catch (_) { }
return {
type: TYPES.TASK_LIST,
list
}
},
// 同步派发:删除执行任务
deleteTaskById(id) {
return {
type: TYPES.TASK_DELETE,
id
}
},
// 同步派发:修改任务状态
updateTaskById(id) {
return {
type: TYPES.TASK_UPDATE,
id
}
}
}
export default taskAction
store/actions/index.js:
import taskAction from "./taskAction";
const action = {
task: taskAction
}
export default action
store/index.js:
import { createStore, applyMiddleware } from 'redux'
import reducer from './reducers/index'
import reduxLogger from 'redux-logger'
import reduxPromise from 'redux-promise'
import reduxThunk from 'redux-thunk'
const store = createStore(
reducer,
applyMiddleware(reduxLogger, reduxPromise, reduxThunk)
)
export default store
在组件中,基于react-redux实现需求
index.js:
...
/* 使用REDUX */
import store from './store/index'
import { Provider } from 'react-redux'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ConfigProvider locale={zhCN}>
<Provider store={store}>
<Task />
</Provider>
</ConfigProvider>
);
Task.jsx
从react-redux中解构出connect函数,将所需要的状态和派发行为对象的方法作为属性传递给要渲染的组件,在组件中获取基于属性传递进来的公共状态和ActionCreator:
import { connect } from 'react-redux'
import action from '@/store/actions/index.js'
...
const Task = function Task(props) {
let { taskList, qeuryAllList, deleteTaskById, updateTaskById } = props
...
}
export default connect(
state => state.task,
action.task
)(Task);
关于Table和数据的处理:
第一次渲染完毕,判断redux中是否有全部的任务;如果没有,则进行异步的派发
useEffect(() => {
if (!taskList) {
queryAllList()
}
}, [])
依赖于redux中的全部任务 & 根据选中的状态信息,从全部任务中筛选出表格需要的数据
useEffect(() => {
if (!taskList) {
setTableData([])
return
}
if (selectedIndex !== 0) {
taskList = taskList.filter(item => {
return +item.state === +selectedIndex
})
}
setTableData(taskList)
}, [taskList, selectedIndex])
submit函数中,每次添加新任务后进行任务派发,重新获取全部任务信息,同步到redux中
const submit = async () => {
try {
await formIns.validateFields()
let { task, time } = formIns.getFieldsValue()
time = time.format('YYYY-MM-DD HH:mm:ss')
setConfirmLoading(true)
let { code } = await addTask(task, time)
if (+code === 0) {
closeModal()
queryAllList()
message.success("恭喜添加成功")
} else {
message.error("很遗憾添加失败")
}
} catch (_) { }
setConfirmLoading(false)
}
删除和更新任务状态的函数removeHandle和updateHandle中,每次通知服务器进行相关操作后不必再从服务器中重新获取数据(task列表),而是进行相应任务派发,修改redux容器中公共状态信息,从而减少应用向服务器请求次数
const removeHandle = async (id) => {
try {
let { code } = await removeTask(id)
if (+code === 0) {
deleteTaskById(id)// 派发任务,删除redux中的数据
message.success("恭喜删除成功")
} else {
message.error("很遗憾删除失败")
}
} catch (_) { }
}
const updateHandle = async (id) => {
try {
let { code } = await completeTask(id)
if (+code === 0) {
updateTaskById(id)// 派发任务,修改redux中的数据
message.success("恭喜更新任务状态成功")
} else {
message.error("很遗憾更新失败")
}
} catch (_) { }
}
优化: 第一次渲染组件获取数据时的loading效果
useEffect(() => {
(async () => {
if (!taskList) {
setTableLoading(true)
await queryAllList()
setTableLoading(false)
}
})()
}, [])
Task.jsx完整代码:
import React, { useState, useEffect } from "react";
import './Task.less';
import { Button, Tag, Table, Popconfirm, Modal, Form, Input, DatePicker, Divider, message } from 'antd'
import { getTaskList, addTask, removeTask, completeTask } from '../api'
import { connect } from 'react-redux'
import action from '@/store/actions/index.js'
// 对日期的处理方法
const zero = function zero(text) {
text = String(text)
return text.length < 2 ? '0' + text : text
}
const formatTime = function formatTime(time) {
let arr = time.match(/\d+/g),
[_, month, day, hour = "00", minute = "00"] = arr
return `${zero(month)}-${zero(day)} ${zero(hour)}:${zero(minute)}`
}
const Task = function Task(props) {
/* 获取基于属性传递进来的公共状态 & ActionCreator */
let { taskList, queryAllList, deleteTaskById, updateTaskById } = props
/* 表格列的数据 */
const columns = [
{
title: '编号',
dataIndex: 'id',
align: 'center',
width: '8%',
},
{
title: '任务描述',
dataIndex: 'task',
ellipsis: true,
width: '50%',
},
{
title: '状态',
dataIndex: 'state',
align: 'center',
width: '10%',
render: text => +text === 1 ? '未完成' : '已完成'
},
{
title: '完成时间',
dataIndex: 'time',
align: 'center',
width: '15%',
render: (_, record) => {
let { state, time, complete } = record
if (+state === 2) time = complete
return formatTime(time)
}
},
{
title: '操作',
dataIndex: 'complete',
render: (_, record) => {
let { id, state } = record
return <>
<Popconfirm
title="您确定要删除此任务吗?"
onConfirm={removeHandle.bind(null, id)}
>
<Button type="link">删除</Button>
</Popconfirm>
{+state !== 2 ? <Popconfirm
title="您确定要将此任务设置为完成吗?"
onConfirm={updateHandle.bind(null, id)}
>
<Button type="link">完成</Button>
</Popconfirm> : null}
</>
}
},
];
/* 初始组件的状态 */
let [selectedIndex, setSelectedIndex] = useState(0),
[tableData, setTableData] = useState([]),
[tableLoading, setTableLoading] = useState(false),
[modalVisible, setModalVisible] = useState(false),
[confirmLoading, setConfirmLoading] = useState(false)
let [formIns] = Form.useForm()
// 第一次渲染完毕,判断redux中是否有全部的任务;如果没有,则进行异步的派发
useEffect(() => {
(async () => {
if (!taskList) {
setTableLoading(true)
await queryAllList()
setTableLoading(false)
}
})()
}, [])
// 依赖于redux中的全部任务 & 根据选中的状态信息,从全部任务中筛选出表格需要的数据
useEffect(() => {
if (!taskList) {
setTableData([])
return
}
if (selectedIndex !== 0) {
taskList = taskList.filter(item => {
return +item.state === +selectedIndex
})
}
setTableData(taskList)
}, [taskList, selectedIndex])
// 关闭对话框
const closeModal = () => {
setModalVisible(false)
setConfirmLoading(false)
formIns.resetFields()
}
// 添加新任务
const submit = async () => {
try {
await formIns.validateFields()
let { task, time } = formIns.getFieldsValue()
time = time.format('YYYY-MM-DD HH:mm:ss')
setConfirmLoading(true)
let { code } = await addTask(task, time)
if (+code === 0) {
closeModal()
// query()
queryAllList()// 派发任务,重新获取全部任务信息,同步到redux中
message.success("恭喜添加成功")
} else {
message.error("很遗憾添加失败")
}
} catch (_) { }
setConfirmLoading(false)
}
/* // 请求数据
const query = async () => {
setTableLoading(true)
try {
let { code, list } = await getTaskList(selectedIndex)
if (+code !== 0) {
list = []
}
setTableData(list)
} catch (_) { }
setTableLoading(false)
}
useEffect(() => {
query()
}, [selectedIndex]) */
// 删除任务
const removeHandle = async (id) => {
try {
let { code } = await removeTask(id)
if (+code === 0) {
// query()
deleteTaskById(id)// 派发任务,删除redux中的数据
message.success("恭喜删除成功")
} else {
message.error("很遗憾删除失败")
}
} catch (_) { }
}
// 更新任务
const updateHandle = async (id) => {
try {
let { code } = await completeTask(id)
if (+code === 0) {
// query()
updateTaskById(id)// 派发任务,修改redux中的数据
message.success("恭喜更新任务状态成功")
} else {
message.error("很遗憾更新失败")
}
} catch (_) { }
}
return <div className="task-box">
{/* 头部 */}
<div className="header">
<h2 className="title">TASK OA 任务管理系统</h2>
<Button type="primary" onClick={() => {
setModalVisible(true)
}}>新增任务</Button>
</div>
{/* 标签 */}
<div className="tag-box">
{['全部', '未完成', '已完成'].map((item, index) => {
return <Tag key={index}
color={selectedIndex === index ? '#1677ff' : ''}
onClick={() => {
setSelectedIndex(index)
}}
>{item}</Tag>
})}
</div>
{/* 表格 */}
<Table dataSource={tableData} columns={columns} loading={tableLoading} pagination={false} rowKey="id"></Table>
{/* 对话框 & 表单 */}
<Modal title="新增任务窗口" open={modalVisible} confirmLoading={confirmLoading} okText="提交信息" keyboard={false} maskClosable={false} onOk={submit} onCancel={closeModal}>
<Divider />
<Form
layout="vertical"
onFinish={() => { }}
onFinishFailed={() => { }}
form={formIns}
initialValues={{ task: '', time: '' }}>
<Form.Item
label="任务描述"
name="task"
validateTrigger="onBlur"
rules={[
{
required: true,
message: '任务描述是必填项'
},
{
min: 6,
message: '输入的内容至少六位'
}
]}>
<Input.TextArea rows={4} />
</Form.Item>
<Form.Item
label="任务预期完成时间"
name="time"
validateTrigger="onBlur"
rules={[
{
required: true,
message: '预期完成时间是必填项'
},
]}>
<DatePicker showTime />
</Form.Item>
</Form>
<Divider />
</Modal>
</div >;
}
export default connect(
state => state.task,
action.task
)(Task);