Form组件的二次封装实践

1,630 阅读3分钟

引言

最近做后台管理时,由于表单项过多、部分表单项需要考虑联动、同时表单数据需要回显;导致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

如下所示:表单完美支持初始值,赋值,联动控制。

31.gif

项目地址

github.com/dong199903/…