redux工程化之redux-toolkit

134 阅读4分钟

redux-toolkit不再使用redux中的 createStore 创建store对象了,而是使用其内部的 configureStore 方法创建store对象,但是原理是相同的,仍需传入一个对象,对象中有两个配置项,一个为reducer,另一个是middleware。

store文件夹下的配置代码

# 安装
npm i @reduxjs/toolkit@1.9.0 -S

image.png

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;

image.png

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;