假设,我们要使用react + Antd(4.24.8)实现如下的添加和编辑用户的功能,同时要做到复用同一个表单组件分别实现添加和编辑
我们可以给出如下的代码和涉及的知识:
- useState, useEffect, useRef,forwardRef, useCallback的使用
- antd表单组件的使用和封装
- 利用react的effect机制刷新状态
- antd Button, Space, Table, message, Switch, Modal, Form, Input, Select组件的使用
贴出代码并给出具体介绍:
UserList.js
UserList组件是承载添加和编辑按钮的组件,根据你的业务把两个按钮对应的点击事件处理好就行,下面给出触发添加和编辑的核心代码和JSX结构
import React, { useState, useEffect, useRef } from 'react'
import { Button, Space, Table, message, Switch, Modal } from 'antd'
import { DeleteOutlined, EditOutlined, ExclamationCircleOutlined } from '@ant-design/icons'
import api from '../../../api'
import UserFrom from '../../../components/user-manage/userFrom'
export default function UserList() {
const [userList, setUserList] = useState([])
const [roleList, setRoleList] = useState([])
const [regionList, setRegionList] = useState([])
const [curUserNode, setCurUserNode] = useState({})
const [isOpen, setIsOpen] = useState(false)
const [isAdd, setIsAdd] = useState(true)
const [confirmLoading, setConfirmLoading] = useState(false)
const addFrom = useRef(null)
useEffect(() => {
getUserList()
getRoleList()
getRegionList()
}, [])
const columns = [
{
title: '操作',
key: 'handle',
render: item => {
return (
<Space size="middle">
<Button
danger
shape="circle"
icon={<DeleteOutlined />}
disabled={item.default}
onClick={() => confirmDelete(item)}
/>
<Button
type="primary"
shape="circle"
icon={<EditOutlined />}
disabled={item.default}
onClick={() => {
setCurUserNode(item)
setIsAdd(false) //不是add则是update
setIsOpen(true)
}}
/>
</Space>
)
}
}
]
const getUserList = async () => {
const { data: res } = await api.getUserList().catch(() => message.error('数据请求失败'))
setUserList(res)
}
const getRoleList = async () => {
const { data: res } = await api.getRoleList().catch(() => message.error('数据请求失败'))
setRoleList(res)
}
const getRegionList = async () => {
const { data: res } = await api.getRegionList().catch(() => message.error('数据请求失败'))
setRegionList(res)
}
const handleAddOk = addFrom => {
setConfirmLoading(true)
const form = addFrom.current
form.validateFields().then(value => {
api
.addUser({
...value,
roleState: true,
default: value.roleId === 1 ? true : false
})
.then(() => {
getUserList()
setConfirmLoading(false)
setIsOpen(false)
message.success('添加成功')
})
.catch(() => message.error('添加失败'))
})
}
const handleEditOk = addFrom => {
setConfirmLoading(true)
const form = addFrom.current
form.validateFields().then(value => {
api
.editUser(
{
...value,
roleState: true,
default: value.roleId === 1 ? true : false
},
curUserNode.id
)
.then(() => {
getUserList()
setConfirmLoading(false)
setIsOpen(false)
message.success('修改成功')
})
.catch(() => message.error('修改失败'))
})
}
return (
<div>
<Button
type="primary"
onClick={() => {
setIsAdd(true)
setIsOpen(true)
}}
>
添加用户
</Button>
<Table dataSource={userList} columns={columns} rowKey={item => item.id} />
<Modal
open={isOpen}
title={isAdd ? '添加用户' : '编辑用户'}
okText={isAdd ? '确认添加' : '确认修改'}
cancelText="取消"
confirmLoading={confirmLoading}
onCancel={() => {
setIsOpen(false)
setIsAdd(true)
}}
onOk={() => {
return isAdd ? handleAddOk(addFrom) : handleEditOk(addFrom)
}}
>
<UserFrom
ref={addFrom}
roleList={roleList}
regionList={regionList}
isAdd={isAdd}
curUser={curUserNode}
></UserFrom>
</Modal>
</div>
)
}
userForm.js
UserForm是封装的表单组件,根据传入参数决定是添加还是编辑
import React, { forwardRef, useCallback, useEffect, useState } from 'react'
import { Form, Input, Select } from 'antd'
const userFrom = forwardRef((props, ref) => {
const [isDisabled, setIsDisabled] = useState(false)
const { regionList, roleList, isAdd, curUser } = props
const [form] = Form.useForm()
const handleRoleChange = useCallback(
value => {
if (value === 1) {
setIsDisabled(true)
form.setFieldValue('region', '')
} else {
setIsDisabled(false)
}
},
[form]
)
const initForm = useCallback(() => {
if (isAdd) {
form.resetFields()
setIsDisabled(false)
} else {
form.setFieldsValue(curUser)
handleRoleChange(curUser.roleId)
}
}, [form, isAdd, curUser, handleRoleChange])
useEffect(() => {
initForm()
//
}, [initForm])
return (
<Form form={form} ref={ref} layout="vertical">
<Form.Item
name="username"
label="用户名"
rules={[
{
required: true,
message: '请输入用户名'
}
]}
>
<Input />
</Form.Item>
<Form.Item
name="password"
label="密码"
rules={[
{
required: true,
message: '请输入密码'
}
]}
>
<Input />
</Form.Item>
<Form.Item
name="roleId"
label="角色"
rules={[
{
required: true,
message: '请选择角色'
}
]}
>
<Select
style={{
width: 150
}}
fieldNames={{ label: 'roleName', value: 'id' }}
onChange={value => {
handleRoleChange(value)
}}
options={roleList}
/>
</Form.Item>
<Form.Item
name="region"
label="区域"
rules={
isDisabled
? ''
: [
{
required: true,
message: '请选择区域'
}
]
}
>
<Select
style={{
width: 150
}}
disabled={isDisabled}
fieldNames={{ label: 'title', value: 'value' }}
options={regionList}
/>
</Form.Item>
</Form>
)
})
export default userFrom
具体解释
1.我们先来看一下Modal模态框的代码
<Modal
open={isOpen}
title={isAdd ? '添加用户' : '编辑用户'}
okText={isAdd ? '确认添加' : '确认修改'}
cancelText="取消"
confirmLoading={confirmLoading}
onCancel={() => {
setIsOpen(false)
setIsAdd(true)
}}
onOk={() => {
return isAdd ? handleAddOk(addFrom) : handleEditOk(addFrom)
}}
>
根据isOpen设置显示隐藏,根据isAdd属性判断是添加还是编辑,设置confirmLoading选择为异步处理模式(就是点击确定后,模态框会等待,需要请求完成后才会关闭)
然后绑定好确定和取消的回调,在取消
时,将状态重置到添加状态,有利于后续清空表单的值;
在确定
时,根据isAdd调对应的回调函数,这两个回调函数在后面解释。
2.然后,我们需要创建出对应添加和编辑的两个触发按钮并设置好点击事件
添加Button:这里我们设置了两个状态add和open,表示当前是添加操作且打开modal框
<Button
type="primary"
onClick={() => {
setIsAdd(true)
setIsOpen(true)
}}
>
添加用户
</Button>
编辑Button:嵌入在Table组件中,点击后主要做了三件事,第一将当前选中的用户对象设置到状态,方便后续传入Form组件
(这里的参数item是antd回传的,item就是Table组件中当前选中行所对应的数据源中的对象)
<Table dataSource={userList} columns={columns} rowKey={item => item.id} />
然后设置isAdd为false表示编辑状态,设置isOpen为true打开模态框
const columns = [
{
title: '操作',
key: 'handle',
render: item => {
return (
<Space size="middle">
<Button
danger
shape="circle"
icon={<DeleteOutlined />}
disabled={item.default}
onClick={() => confirmDelete(item)}
/>
<Button
type="primary"
shape="circle"
icon={<EditOutlined />}
disabled={item.default}
onClick={() => {
setCurUserNode(item)
setIsAdd(false) //不是add则是update
setIsOpen(true)
}}
/>
</Space>
)
}
}
]
3.接下来将UserForm组件包裹在Modal组件中
<UserFrom
ref={addFrom}
roleList={roleList}
regionList={regionList}
isAdd={isAdd}
curUser={curUserNode}
></UserFrom>
这里我们传入了很多属性,一一进行解释
ref:我们使用const addFrom = useRef(null)
创建了一个引用,并绑定到ref属性上,为了在UserForm组件内部使用forwardRef
进行透传,将UserForm组件进行包裹:
const userFrom = forwardRef((props, ref) => {
const [form] = Form.useForm()
return (
<Form form={form} ref={ref} layout="vertical"></Form>
)
})
export default userFrom
这样可以在第二个参数中获得ref,并将透传的ref绑定在Form组件上,这样在父组件UserList中就可以拿到Form的引用了
roleList、regionList:表单项需要用到的数据源
isAdd:标识添加编辑
curUser:当前选中的用户,编辑状态下进行数据的回填
OK,接下来让我们进行UserForm组件的编写
4.具体表单项的编码请查询上方详细代码,这里具体解释需要进行联动的两个表单项
这里有一个具体的业务需求:当角色为超级管理员时,则不需要选择区域了,需要禁用掉区域select,当选择其他角色时,区域select又需要打开,同时也需要同步修改验证规则,因为我们设置了不能为空
<Form.Item
name="roleId"
label="角色"
rules={[
{
required: true,
message: '请选择角色'
}
]}
>
<Select
style={{
width: 150
}}
fieldNames={{ label: 'roleName', value: 'id' }}
onChange={value => {
handleRoleChange(value)
}}
options={roleList}
/>
</Form.Item>
<Form.Item
name="region"
label="区域"
rules={
isDisabled
? ''
: [
{
required: true,
message: '请选择区域'
}
]
}
>
<Select
style={{
width: 150
}}
disabled={isDisabled}
fieldNames={{ label: 'title', value: 'value' }}
options={regionList}
/>
</Form.Item>
于是,这里我们通过isDisabled状态来控制禁用状态,对角色select绑定监听事件handleRoleChange(value)
,逻辑比较简单,就是判断选择为超级管理员时,设置禁用状态并将region区域置为空即可
const handleRoleChange = useCallback(
value => {
if (value === 1) {
setIsDisabled(true)
form.setFieldValue('region', '')
} else {
setIsDisabled(false)
}
},
[form]
)
5.接下来是非常重要的一步,如何判断是添加还是编辑呢?并且做出对应的逻辑处理?
const initForm = useCallback(() => {
if (isAdd) {
form.resetFields()
setIsDisabled(false)
} else {
form.setFieldsValue(curUser)
handleRoleChange(curUser.roleId)
}
}, [form, isAdd, curUser, handleRoleChange])
useEffect(() => {
initForm()
//
}, [initForm])
我们创建了一个initForm函数,该函数首先判断传入的isAdd
如果是添加:则重置表单属性,并取消禁用
如果是编辑:根据传入的curUser回填属性,这里注意,我们又调了一次handleRoleChange方法,这里的目的就是需要根据选择的数据判断是否需要禁用区域选择框。
initForm这一步的操作非常重要,因为我们在useEffect中依赖设置了initForm,而initForm的依赖又设置了isAdd, curUser,这样每次点击添加或者编辑,传入的props就会改变,从而引起initForm改变,从而引起useEffect重新执行
这一切都是为了每次点击编辑和添加时,可以获得对应的最新状态,如果不这样操作,当点击了编辑后,联动效果无法产生,同时回填的数据也会保存,再点击添加时,数据就会回显了,这显然不符合预期
6.这一切都完成后,数据我们就处理完了,只需要分别去调用添加和编辑的回调就可以了
const handleAddOk = addFrom => {
setConfirmLoading(true)
const form = addFrom.current
form.validateFields().then(value => {
api
.addUser({
...value,
roleState: true,
default: value.roleId === 1 ? true : false
})
.then(() => {
getUserList()
setConfirmLoading(false)
setIsOpen(false)
message.success('添加成功')
})
.catch(() => message.error('添加失败'))
})
}
const handleEditOk = addFrom => {
setConfirmLoading(true)
const form = addFrom.current
form.validateFields().then(value => {
api
.editUser(
{
...value,
roleState: true,
default: value.roleId === 1 ? true : false
},
curUserNode.id
)
.then(() => {
getUserList()
setConfirmLoading(false)
setIsOpen(false)
message.success('修改成功')
})
.catch(() => message.error('修改失败'))
})
}
其实逻辑都很简单,只是调用的接口不同
首先setConfirmLoading打开loading,然后通过ref引用拿到表单对象 const form = addFrom.current
接着调用form.validateFields()进行表单验证,这是一个Promise,在then回调中调用对应的接口,接口调用成功后,在接口的then回调中取消loading,并在此请求user数据源刷新页面,关闭模块框即可!
刚开始写文,可能很多地方没有说清楚,欢迎朋友们指正!