由于文章太长了,导致编辑器太卡,所以不得已将文章分成两部分。这部分是【记一次前后端分离开发经验】的第二部分,第一部分的链接为:
如果您第一次点开这篇文章,劳驾您移步至:记一次前后端分离开发经验 -- 后端部分
前端部分
这部分对应的环节如下所示:
1. 搭建前端项目结构
shell
npx create-react-app mail-front
cd mail-front
code .
yarn add @ant-design/pro-table antd @ant-design/icons mobx@6.12.0 mobx-react@9.1.0 mobx-react-lite@4.0.5 lodash@4.17.11 moment
rm -rf src
mkdir -p src/Mail
touch src/index.js src/App.js src/Mail/UserModel.jsx src/Mail/ListTable.jsx src/Mail/MailList.jsx
mkdir src/store
touch src/store/index.jsx src/store/Mail.jsx
git add .
git commit -m "project init"
注意:这里使用jsx和js是一样的!看个人喜好。
2. 入口文件中的内容
由于刚把入口文件和App.js删了,所以需要手动将index.js和App.js中的内容补充进来
js
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />
);
然后是App.js中的内容:
js
import ListTable from './Mail/ListTable';
const App = () => {
return (
<ListTable />
)
}
export default App;
3. 业务组件 ListTable.js 的构造
在ListTable.js中将proTable的实现分成了三个大的部分:
- 表格主体dom结构渲染
- proTable的列的配置
- 网络请求相关的函数由mobx统一管理
- 添加或者编辑用户的弹窗
3.1 表格主体dom结构渲染
此部分功能由src/Mail/ListTable.jsx本身提供,对应的代码为:
js
import React, { useState, useRef, useContext } from 'react';
import ProTable from '@ant-design/pro-table';
import { Button } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import UserModal from './UserModal.jsx';
import Column from './MailList';
import { StoreContext } from '../store';
const ListTable = props => {
const [addInfo, setAddInfo] = useState({
visible: false,
init: {},
});
const [pageSize, setPageSize] = useState(10);
const defaultPagination = {
pageSize,
showTotal: (total, range) => `${range[0]}-${range[1]} of ${total} items`,
showSizeChanger: true,
showQuickJumper: true,
size: 'small',
};
const actionRef = useRef(null);
const getLatestData = () => {
actionRef.current?.reload();
};
const { mailStore } = useContext(StoreContext);
return (
<>
<ProTable
actionRef={actionRef}
headerTitle="interaction demo"
columns={Column(getLatestData, setAddInfo)}
rowKey="key"
search={false}
options={false}
toolBarRender={() => [
<Button
key="addUser"
type="primary"
onClick={(...args) => {
setAddInfo({
visible: true,
init: {
id: undefined,
mailAddress: undefined,
recipientName: undefined,
status: 0,
},
});
}}
>
<PlusOutlined /> Add User
</Button>,
]}
pagination={defaultPagination}
request={async (...args)=>{
const rst = await mailStore.request(...args);
const {pageSize} = rst;
setPageSize(pageSize);
return rst;
}}
/>
<UserModal
visible={addInfo.visible}
onCancel={() => {
setAddInfo({
visible: false,
init: {
id: undefined,
mailAddress: undefined,
recipientName: undefined,
status: 0,
},
});
}}
originalData={addInfo.init}
updateParent={getLatestData}
/>
</>
);
};
export default ListTable;
3.2 proTable列的配置
配置ant-design/pro-table组件所需要的列对象,由于配置项比较复杂,所以单独拆到src/Mail/MailList.jsx
文件中完成。
该文件的内容为:
js
// src/Mail/MailList.jsx
import React, { useContext, useRef, useCallback } from 'react';
import moment from 'moment';
import { Switch, Space, Popconfirm, message } from 'antd';
import { StoreContext } from '../store/index.jsx';
import { debounce } from 'lodash';
const Column = (
getLatestData,
setAddInfo,
) => {
const { mailStore } = useContext(StoreContext);
const hasClicked = useRef(false);
const doSomething = row => {
mailStore.testHandle(getLatestData, row);
};
const debouncedFunction = useCallback(
debounce(() => {
hasClicked.current = false;
}, 3000),
[],
);
const handleClick = row => {
if (!hasClicked.current) {
doSomething(row);
hasClicked.current = true;
debouncedFunction();
} else {
message.warning('Send too frequently. Please try again later.');
}
};
return [
{
title: 'Mail address',
dataIndex: 'mailAddress',
key: 'mailAddress',
},
{
title: 'User name',
dataIndex: 'recipientName',
key: 'recipientName',
},
{
title: 'Modified Time',
dataIndex: 'updateTime',
render: (_, row) => {
const { updateTime } = row;
if (updateTime)
return (
<span style={{ textWrap: 'noWrap' }}>
{moment(updateTime).format('MM/DD, YYYY HH:mm:ss')}
</span>
);
return '-';
},
},
{
title: 'Status',
dataIndex: 'status',
key: 'status',
render: (_, row) => (
<Switch
checked={row.status}
onChange={val => {
mailStore.statusHandle(getLatestData, val, row);
}}
/>
),
},
{
title: 'Action',
key: 'action',
valueType: 'option',
render: (_, row) => {
return (
<Space size="middle">
<a
onClick={() =>{
console.log('row:', row)
setAddInfo({
visible: true,
init: { ...row },
})
}
}
>
Edit
</a>
<Popconfirm
key="delete"
title={'sure to delete?'}
onConfirm={() => {
mailStore.deleteHandle(getLatestData, row);
}} // dangerous
>
<a
style={{
marginRight: 16,
textWrap: 'nowrap',
color: '#FF4D4F',
}}
>
Delete
</a>
</Popconfirm>
</Space>
);
},
},
];
};
export default Column;
上述代码定义这个表格具有Mail address
, User name
, Modified Time
, Status
, 和Action
共五列内容。而这五列的内容则是通过请求后端数据接口获取并渲染的,渲染过程由ProTable
组件封装起来了,只需按照组件的使用规范进行使用即可。
3.3 使用mobx统一管理网络请求
使用mobx统一管理此功能对应的各种网络请求,这样做的话能够将统一业务的相关代码放在一起,便于管理。当然也可以不用mobx,但是在我实际开发过程中mobx还管理其他状态,因此索性就让mobx将网络请求一起管理了。
src/store/index.js中的内容
实际开发中,不可能只有一个store,因此需要将多个store结合起来统一向外暴露,因此index.js中的内容为:
js
// src/store/index.js
import React from 'react';
import { mailStore } from './Mail.jsx';
export const store = {
mailStore,
};
export const StoreContext = React.createContext(store);
src/store/Mail.jsx中的内容
这个文件管理了所有此示例中请求相关的函数:
js
// Import necessary libraries
import { makeAutoObservable } from 'mobx';
import { message } from 'antd';
import axios from 'axios';
// Define the base URL for API requests
const BASE_URL = 'http://192.168.190.76:7777';
// Define the paths for different API endpoints
const Delete_Path = `${BASE_URL}/delete?id=`;
const Status_Path = `${BASE_URL}/chgStatus`;
const Submit_Path = `${BASE_URL}/save`;
const Update_Path = `${BASE_URL}/update`;
const Request_Path = `${BASE_URL}/pageList`;
// Create a class for managing mail-related state and actions using MobX
class Mail {
constructor() {
// Make the class observable to track state changes
makeAutoObservable(this);
}
// Handle delete operation
deleteHandle(getLatestData, row) {
if (!row?.id) return;
axios
.get(`${Delete_Path}${row.id}`)
.then(res => {
getLatestData();
});
}
// Handle status change operation
statusHandle(getLatestData, val, row) {
axios
.post(Status_Path, {
...row,
status: val ? 1 : 0,
})
.then(res => {
getLatestData();
});
}
// Handle form submission
onSubmit(values, originalData, onCancel, updateParent) {
axios
.post(Submit_Path, {
...originalData,
...values,
})
.then(res => {
if (updateParent) updateParent();
const { success, msg } = res?.data ?? {};
if (!success) {
message.error(msg);
return false;
}
if (success) message.success('Success');
if (onCancel) onCancel();
return true;
});
}
// Handle form update
onUpdate(values, originalData, onCancel, updateParent) {
axios
.post(Update_Path, {
...originalData,
...values,
})
.then(res => {
if (updateParent) updateParent();
const { success, msg } = res?.data ?? {};
if (!success) {
message.error(msg);
return false;
}
if (success) message.success('Success');
if (onCancel) onCancel();
return true;
});
}
// Perform API request to retrieve data
async request(params = {}) {
const { current, pageSize, ...restParams } = params; // Extract search parameters
const payload = {
...restParams,
};
const response = await axios.post(
Request_Path,
{
body: payload,
query: {
current,
size: pageSize,
},
},
);
// Extract relevant data from the API response
const {
data: { records, total, current: pages },
} = response?.data;
// Return formatted data
return {
data: records,
success: true,
pages,
total,
};
}
}
// Create an instance of the Mail class to be used as a MobX store
export const mailStore = new Mail();
3.4 添加或者编辑用户的弹窗
使用antd中的Model组件完成添加或者编辑用户信息时候的弹窗,该部分功能由src/Mail/UserModel.jsx
组件提供,其文件内容如下所示:
js
import React, { useEffect, useContext } from 'react';
import { Modal, Form, Input, Button } from 'antd';
import { StoreContext } from '../store/index.jsx';
// Component for handling user modal (Add/Edit)
const UserModal = props => {
// Destructure props for readability
const { onCancel, updateParent, name } = props;
// Create a form instance using Ant Design Form
const [form] = Form.useForm();
// Get the mailStore instance from the context
const { mailStore } = useContext(StoreContext);
// Extract originalData and check if it's in edit mode
const originalData = props?.originalData ?? {};
const isEditMode = !!originalData?.id;
// useEffect to reset or set form fields based on edit mode and original data
useEffect(() => {
if (!isEditMode) form.resetFields();
if (isEditMode) form.setFieldsValue(originalData);
}, [isEditMode, originalData]);
// Define form item layout for Ant Design Form
const formItemLayout = {
labelCol: { span: 6 },
wrapperCol: { span: 18 },
};
// Handle form submission
const onFinish = values => {
// Execute appropriate action based on edit mode
const _exec = isEditMode
? values => void mailStore.onUpdate(values, originalData, onCancel, updateParent)
: values => void mailStore.onSubmit(values, originalData, onCancel, updateParent);
_exec(values);
};
// Render the UserModal component
return (
<Modal
title={isEditMode ? 'Edit' : 'Add'}
visible={props.visible}
onCancel={props.onCancel}
destroyOnClose
footer={[
<Button key="submit" type="primary" onClick={() => form.submit()}>
{isEditMode ? 'Update' : 'Submit'}
</Button>,
]}
>
<Form
{...formItemLayout}
form={form}
onFinish={onFinish}
>
{/* Form items for mail address and user name */}
<Form.Item
label="Mail address"
name="mailAddress"
rules={[
{ required: true, message: 'Please enter' },
{
type: 'email',
message: 'Invalid email address',
},
]}
initialValue={isEditMode ? originalData?.mailAddress : undefined}
>
<Input placeholder="Please enter" />
</Form.Item>
<Form.Item
label="User name"
name="recipientName"
rules={[{ required: true, message: 'Please enter' }]}
initialValue={isEditMode ? originalData?.recipientName : undefined}
>
<Input placeholder="Please enter" maxLength={50} />
</Form.Item>
</Form>
</Modal>
);
};
export default UserModal;
4. 效果展示
下面的视频展示了最终效果,在这个视频中你可以看到的前后端交互内容包括:
- 请求列表数据并按照编辑时间倒序排列
- 新增用户
- 编辑用户
- 删除用户
- 列表分页
- 修改用户状态
结尾
我真诚的希望得到大佬的指导和建议。如果喜欢的人多的话,我会把代码上传至github,或者说当成个人的笔记也是极好的。