redux-toolkit不再使用redux中的 createStore 创建store对象了,而是使用其内部的 configureStore 方法创建store对象,但是原理是相同的,仍需传入一个对象,对象中有两个配置项,一个为reducer,另一个是middleware。
store文件夹下的配置代码
# 安装
npm i @reduxjs/toolkit@1.9.0 -S
import { configureStore } from '@reduxjs/toolkit';
import reduxLogger from 'redux-logger';
import reduxPromise from 'redux-promise';
import reduxThunk from 'redux-thunk';
import taskSliceReducer from './features/taskSlice';
/* 基于切片机制,把reducer和actionCreator混合在一起了 */
const store = configureStore({
// 指定reducer
reducer: { // 按模块管理各个切片
task: taskSliceReducer
},
// 使用中间件,不指定任何中间件,其内部默认集成redux-thunk,若我们指定了,则指定的会覆盖默认集成的
middleware: [reduxLogger, reduxPromise, reduxThunk]
})
export default store;
import { createSlice } from '@reduxjs/toolkit';
import { getTaskList } from '../../api';
import { message } from 'antd';
const taskSlice = createSlice({
// 设置切片的名字
name: 'task',
// 设置切片对应reducer中的初始状态
initialState: {
taskList: null
},
// 编写不同业务逻辑下,对公共状态的更改
reducers: {
getAllTaskList(state, action) {
// state: 当前模块下的状态(基于 immer 库管理,无需自己再克隆了)
// action: 派发的行为对象,无需考虑行为标识的问题了;传递的都是以 action.payload 传递进来的值
state.taskList = action.payload;
},
removeTask(state, { payload }) { // payload:接受传递进来的,要删除的那一项的id
let taskList = state.taskList;
if (!Array.isArray(taskList)) {
return;
}
state.taskList = taskList.filter(item => (+item.id !== +payload));
},
updateTask(state, { payload }) {
let taskList = state.taskList;
if (!Array.isArray(taskList)) {
return;
}
state.taskList = taskList.map(item => {
if(+item.id === +payload) {
item.state = 2;
item.complete = new Date().toLocaleString('zh-CN');
}
return item;
});
}
}
});
// 从切片中获取actionCreator:此处解构的方法和上面reducers中的方法,仅仅是函数名字相同;
// 方法执行,返回需要派发的行为对象;后期可以基于dispatch进行任务派发即可!!
export let { getAllTaskList, removeTask, updateTask } = taskSlice.actions;
// 实现异步派发 redux-thunk
export const getAllTaskListAsync = () => {
return async (dispatch) => {
let list = [];
try {
const res = await getTaskList();
if(+res.code === 0) {
list = res.list;
} else {
message.error('获取列表失败~');
}
} catch(_) {
} finally {
dispatch(getAllTaskList(list));
}
}
}
// 从切片中获取reducer
export default taskSlice.reducer;
组件中引入store获取状态与实现派发
根组件下
import ReactDOM from 'react-dom/client';
import Task from './views/Task';
/* 使用ANTD组件库 */
import { ConfigProvider } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import './index.less';
import { Provider } from 'react-redux';
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ConfigProvider locale={zhCN}>
<Provider store={store}>
<Task />
</Provider>
</ConfigProvider>
);
Task组件下
import React, { useEffect, useState, useRef } from "react";
import { Button, Modal, Form, Table, DatePicker, Popconfirm, Tag, Input, message } from 'antd';
import { addTask, removeTask, completeTask } from '../../api';
import TaskWrapper from './TaskStyle';
import { useSelector, useDispatch } from 'react-redux'; // 不再使用connect函数
import * as taskSliceActions from '../../store/features/taskSlice';
const zero = function (text) {
text = String(text);
return text.length < 2 ? '0' + text : text;
};
const formatTime = function (time) {
let arr = time.match(/\d+/g),
[, month, day, hours = '00', minutes = '00'] = arr;
return `${zero(month)}-${zero(day)} ${zero(hours)}:${zero(minutes)}`;
};
const Task = () => {
/* 初始组件的状态 */
const [tableData, setTableData] = useState([]),
[tableLoading, setTableLoading] = useState(false),
[modalVisible, setModalVisible] = useState(false),
[confirmLoading, setConfirmLoading] = useState(false), // 模态框确定按钮 loading
[selectedIndex, setSelectedIndex] = useState(0); // 选中的tab
// ref获取表单实例
const formIns = useRef(null);
/* 获取公共状态和派发的方法 */
let { taskList } = useSelector(state => state.task),
dispatch = useDispatch();
// 第一次渲染完毕,判断redux中是否有全部任务,如果没有,则进行异步派发
useEffect(() => {
(async () => {
if(!taskList) {
// 如果是异步派发,则dispatch执行返回值是promise实例;是同步派发,则返回派发的行为对象
try {
setTableLoading(true);
await dispatch(taskSliceActions.getAllTaskListAsync());
} catch(_) {
} finally {
setTableLoading(false);
}
}
})();
}, []);
// 依赖redux中的全部状态 和 选中的信息,从全部任务中筛选出表格需要的数据
useEffect(() => {
if(!taskList) {
setTableData([]);
return;
}
if(selectedIndex !== 0) {
setTableData(taskList.filter(item => +item.state === selectedIndex));
return;
}
setTableData(taskList);
}, [taskList, selectedIndex]);
/* 表格列的数据 */
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: '操作',
render: (_, record) => {
let { id, state } = record;
return <>
<Popconfirm title="您确定要删除此任务吗?"
onConfirm={() => {
handleRemove(id)
}}>
<Button type="link">删除</Button>
</Popconfirm>
{+state !== 2 ? <Popconfirm title="您确把此任务设置为完成吗?"
onConfirm={() => {
handleUpdate(id);
}}>
<Button type="link">完成</Button>
</Popconfirm> : null}
</>;
}
}];
// 模态框关闭
const closeModal = () => {
setModalVisible(false);
setConfirmLoading(false);
formIns.current.resetFields();
}
// 模态框点击确定
const submit = async () => {
try {
setConfirmLoading(true);
await formIns.current.validateFields();
// 通过表单校验
let { task, time } = formIns.current.getFieldsValue();
time = time.format('YYYY-MM-DD HH:mm:ss');
const { code } = await addTask(task, time);
if(+code === 0) {
message.success('新增任务成功!');
closeModal();
try {
setTableLoading(true);
await dispatch(taskSliceActions.getAllTaskListAsync());
} catch(_) {
} finally {
setTableLoading(false);
}
} else {
message.error('新增任务失败!');
}
} catch(_) {
} finally {
setConfirmLoading(false);
}
}
// 删除此任务
const handleRemove = async (id) => {
try {
const { code } = await removeTask(id);
if(code === 0) {
// 同步派发,dispatch方法返回值为 {type: 'task/updateTask', payload: 5}
dispatch(taskSliceActions.removeTask(id));
message.success('删除成功!');
} else {
message.error('删除失败!');
}
} catch (_) {
}
}
// 完成此任务
const handleUpdate = async (id) => {
try {
const { code } = await completeTask(id);
if(code === 0) {
dispatch(taskSliceActions.updateTask(id));
message.success('修改成功!');
} else {
message.error('修改失败!');
}
} catch (_) {
}
}
return <TaskWrapper>
{/* 头部 */}
<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);
/*
// 与函数组件不同的是,类组件执行setState不管状态值跟之前一样还是不一样,一定会都会经历shouldComponentUpdate->componentWillUpdate->render->componentDidUpdate这一套流程
// 除非类组件手动让它继承PureComponent或者在shouldComponentUpdate中对新老属性和状态进行比较是否一致,不一致才进行更新
if(+selectedIndex !== index) { // setXxx本身具有优化项
setSelectedIndex(index);
}
*/
}}>
{item}
</Tag>;
})}
</div>
{/* 表格 */}
<Table dataSource={tableData}
columns={columns}
loading={tableLoading}
pagination={false}
rowKey="id" />
{/* 对话框 & 表单 */}
<Modal title="新增任务窗口"
open={modalVisible} // 打开/关闭模态框标志
confirmLoading={confirmLoading} // 确定按钮loading
keyboard={false} // 不支持esc关闭模态框
maskClosable={false} // 不支持点击遮罩层关闭模态框
okText="确认提交"
onCancel={closeModal}
onOk={submit}>
<Form
ref={formIns}
layout="vertical"
initialValues={{ task: '', time: '' }}
validateTrigger="onBlur">
<Form.Item
label="任务描述"
name="task" // 作为表单数据对象收录的属性名
rules={[
{ required: true, message: '任务描述是必填项' },
{ min: 6, message: '输入的内容至少6位及以上' }
]}>
<Input.TextArea rows={4}></Input.TextArea>
</Form.Item>
<Form.Item
label="预期完成时间"
name="time"
rules={[
{ required: true, message: '预期完成时间是必填项' }
]}>
<DatePicker showTime />
</Form.Item>
</Form>
</Modal>
</TaskWrapper>
}
export default Task;