引言
最近做后台管理时,由于表单项过多、部分表单项需要考虑联动、同时表单数据需要回显;导致From整体结构比较臃肿,存在大量重复的Form.Item。老大在cr时恨不得给我一拳,于是让我尝试封装Form配置化组件来提高组件的可拓展能力。
期望结构
老大期望将Form组件(公司自己的组件库,Form用法和antd的Form几乎一样)封装成antd的Table这种配置化设计
Table组件
const dataSource = [
{ key: '1', name: '胡彦斌', age: 32, address: '西湖区湖底公园1号', },
{ key: '2', name: '胡彦祖', age: 42, address: '西湖区湖底公园1号', },
];
const columns = [
{ title: '姓名', dataIndex: 'name', key: 'name', },
{ title: '年龄', dataIndex: 'age', key: 'age', },
{ title: '住址', dataIndex: 'address', key: 'address', },
];
<Table dataSource={dataSource} columns={columns} />;
期望的Form结构
期望封装的Form组件使用方式如下,Form组件传递4个主要的参数
-
value :form组件的初始化值
-
setValue:form组件数据更新的setState
-
config:form内部的Form.item表单配置项数组
-
submit:点击提交后触发的逻辑,包含表单校验与数据获取
const formVal = {name:'dzp',age:24,love:'eat'} const formList = [ { key: 'name', label: '姓名', component: 'Input' }, { key: 'age', label: '年龄', component: 'Input' }, { key: 'love', label: '爱好', component: 'Radio' }, ] <MyForm config={formList} value={formVal} setValue={setFormVal} submit={submit} > </MyForm>
设计实践
整体结构逻辑还是比较清晰的,根据外界传递的config数组对象渲染对应的表单项目。 表单数据更新时,借助setState修改外界传递的state数据。表单提交时,将校验的状态和表单数据传递给外界的submit函数。
MyForm组件
import React,{ useEffect,useRef } from "react";
import {Form,Input,Radio,Select,Switch,Button} from "antd";
type Props = {
config:Array<any> //配置数组
value:any //表单初始数据
setValue:(e:any)=>void //表单数据更新
submit:(e:any,v?:any)=>void //表单数据提交
} & Partial<{
children:React.ReactNode
}>
const Forms = (props:Props) => {
const { config,value,setValue,submit } = props;
const formRef = useRef<any>()
//获取form初始化数据
useEffect(()=>{
formRef.current?.setFieldsValue(value);
},[value])
//form提交后验证通过
const onFinish = (values: any) => {
submit(true,value)
};
//form提交后验证失败
const onFinishFailed = (errorInfo: any) => {
submit(false)
};
//form重置
const reset = () => {
formRef.current?.resetFields();
setValue({})
}
//根据配置数组->生产Form.item
const makeComponent = (com:string,list:Array<any>):React.ReactNode => {
switch (com) {
case 'Input':
return <Input></Input>
case 'Select':
return (
<Select>
{list.map(item=>(
<Select.Option key={item.value} value={item.value}>{item.label}</Select.Option>
))}
</Select>
)
case 'Radio':
return (
<Radio.Group>
{ list.map(item=>(
<Radio key={item.value} value={item.value}>{item.label}</Radio>
))}
</Radio.Group>
)
case 'Switch':
return (
<Switch/>
)
default :
return null;
}
}
return (
<Form
ref={formRef}
onValuesChange={e=>{
setValue({...value,...e})
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
>
<>
{ config.length>0 && config.map(item=>{
const {
key,
label,
component,
hidden,
list,
rules,
visible
} = item;
//visible字段存在代表该表单项需要依赖其他表单项进行展示或隐藏
let isHasVisible = Object.keys(item).includes('visible')
if(!isHasVisible || isHasVisible && visible)
return (
<Form.Item
label={label}
key={key}
name={key}
hidden={hidden}
rules={rules}
>
{makeComponent(component,list)}
</Form.Item>
)
})}
<Form.Item>
<Button
onClick={()=>{
formRef.current.submit()
}}
>
提交
</Button>
<Button onClick={reset}>重置</Button>
</Form.Item>
</>
</Form>
)
}
export default Forms;
导入使用
config.tsx
定义表单配置数组项,表单配置数组的key和Form.Item需要传递的key几乎一模一样,同时为了拓展表单联动功能,在需要联动展示的表单项配置上添加visiable(Boolean)来控制联动表单的展示与隐藏。
export default (params:any) => {
const {
selectList,
radioList,
formVal
} = params;
return [
{
key: 'projectId',
label: 'projectId',
component: 'Input',
hidden: true,
},
{
key: 'name',
label: 'name',
component: 'Input',
rules:[
{ required: true, message: 'Please input your username!' }
],
},
{
key: 'name2',
label: 'name2',
component: 'Input',
rules:[
{ required: true, message: 'Please input your username!' }
],
visible:formVal.name==='dzp'
},
{
key: 'sex',
label: 'sex',
component: 'Select',
rules:[
{ required: true, message: 'Please input your sex!' }
],
list:selectList
},
{
key: 'radio',
label: 'radio',
component: 'Radio',
rules:[
{ required: true, message: 'Please input your radio!' }
],
list:radioList
},
{
key: 'switch',
label: 'switch',
component: 'Switch',
rules:[
{ required: true, message: 'Please input your switch!' }
],
},
]
}
App.tsx
import React, { useEffect,useState } from "react";
import Forms from "./components/Forms/index";
import config from "./config";
function App() {
//表单初始化数据
const [formVal,setFormVal] = useState<any>({name:'dzp',projectId:1,sex:'lihe'})
//select组件需要的数据
const [selectList,setSelectList] = useState<any>([])
//radio组件需要的数据
const [radioList,setRadioList] = useState<any>([])
//表单提交
const submit = (v:Boolean,e:any) => {
if(v) {
console.log('最后的表单数据',e)
}else{
console.log('验证失败')
}
}
useEffect(()=>{
//模拟接口请求表单数据和select,radio组件依赖的数据
setTimeout(() => {
setFormVal({
...formVal,name:'dzp100'
})
setSelectList([{label:'小米',value:'xiao'},{label:'大米',value:'big'}])
setRadioList([{label:'man',value:'男'},{label:'women',value:'女'}])
}, 3000);
},[])
return (
<div className="App">
<Forms
config={config({
selectList,
radioList,
formVal
})}
value={formVal}
setValue={setFormVal}
submit={submit}
>
</Forms>
</div>
);
}
export default App;
效果
从config配置数组可以知道,我们每个表单项都是必填的,并且projectId是隐藏项,name2表单项的存在与否依赖于表单项name的值是否等于dzp
如下所示:表单完美支持初始值,赋值,联动控制。