• 基于antd封装一个高可用form组件 减少cv代码导致的bug


    引言

    在开发中台过程中 我们的原型中有很多表单,antd有表单组件,但是粒度比较细,就单纯组件而言,无可厚非,但是在开发过程中,可能会造成代码不够聚合,有些表单公共逻辑无法提取,copy paste比较多,所以可以加以封装,搞一个兼容性和扩展性都契合项目本身的组件。

    主要思路

    我简单查阅了一番资料,参考了一下别人的封装方式,决定以ColFormItem作为基本单元,进行表单的搭建,主要原因

    • 可以将FormItem的信息和Col的信息以对象方式传入,这样我们可以更加专注于组件的包含的信息 减少CV代码可能导致的bug
    • Col可以进行布局的调整,可以调整表单单元位置和所占宽度
    • 表单的组件形式是不定的,可能是input也有可能是select,所以可以进行外部传入,通用属性可以内部修改
    • 复用性高,可以用它来实现一个纯提交表单 和列表组件结合成可搜索表单 或者其他任何项目里需要自定义的一个表单

    实现细节

    抽象的FormItemInfo

    export interface FormItemInfo {
      name: string, //label
      id: string, // 属性
      colLayOut?: object,// 列布局
      formItemLayout?: object,// 表单项布局
      Comp?: any, // 传入的组件 不传默认input
      ruleArr?: RuleObj[],// 验证规则
      initialValue?: string, // 初始值
      required?: boolean, // 是否必填
      CompExtraProps?: object // 传入组件额外属性
      isDetail?: boolean //是否需要被getFieldDecorator托管
    }
    
    

    baseForm组件

     * @param name 表单项label
     * @param id 属性字段名
     * @param colLayOut 列布局
     * @param formItemLayout 表单项布局
     * @param Comp 传入的组件 不传默认input
     * @param ruleArr 验证规则 长度只需传入{max:xxx}验证信息是统一的
     * @param initialValue 初始值 编辑时推荐使用antd的mapPropsToFields 不要手动设置回显值
     * @param required 是否必填 默认否 因为要统一写验证提示
     * @param CompExtraProps 组件额外属性
      const mapToFormItem = (val: FormItemInfo) => {
     ......
     // 是否传入组件
        const hasCompType = Comp && Comp.type
        // 根据组件类型 给出提示文字
        const msg = getMsgByCompType(hasCompType && hasCompType.name, name)
        // 判断是不是select组件 是的话 调整宽度样式
        const mixStyle = fixStyleByCompType(hasCompType && hasCompType.name)
        // 生成验证规则
        const rules = getFormRules(ruleArr || [], required || false, name)
    
      return (
          <Col {...(colLayOut || defaultColSpan)} key={id}>
            {!isDetail ?
              <FormItem label={name} key={id} {...(formItemLayout || {})}>
                {/*用cloneElement方法给传入的组件加新属性*/}
                {
                  getFieldDecorator(id, {
                    initialValue: initialValue || '',
                    rules,
                  })(Comp ? React.cloneElement(Comp, { placeholder: msg, ...mixStyle, ...CompExtraProps }) : <Input type="text" placeholder={msg}/>)
                }
              </FormItem> : <FormItem label={name} key={id} {...(formItemLayout || {})}> {Comp}</FormItem>}
          </Col>
        )
    }
    

    统一方法

    • 根据组件类型 给出提示文字
      /**根据组件类型 给出提示文字
       * @param type 组件类型 根据传入组件的函数名判断 在map里维护
       * @param name label名称
       * @returns 提示文字
       * */
      const getMsgByCompType = (name: string, type?: string): string => {
        if (!type) {
          return `请输入${name}`
        }
        const map: { [props: string]: string } = {
          Select: '请选择',
          Input: '请输入',
          default: '请输入',
        }
        return `${map[type] || map.default}${name}`
      }
    
    
    
    • 生成验证规则
      // 生成验证规则
     * @param ruleArr 验证规则 长度只需传入{max:xxx}验证信息是统一的
     * @param name 表单项label
     * @param required 是否必填 默认否 因为要统一写验证提示
      const getFormRules = (val: RuleObj[], required: boolean, name: string) => {
        // 根据max生成最长输入提示
        const ruleArr: object[] = []
        val.forEach(item => {
          if ('max' in item) {
            ruleArr.push({ ...item, message: `输入信息不能超过${item.max}字` })
          } else {
            ruleArr.push(item)
          }
        })
        // 根据name生成报错提示
        return [{ required: required, message: `${name}不能为空` }, ...ruleArr]
    
    
    • 根据组件类型 给出统一样式修改
      /**根据组件类型 给出统一样式修改
       * @param type 组件类型 根据传入组件的函数名判断 在map里维护
       * @returns 样式对象
       * */
      const fixStyleByCompType = (type?: string): object => {
        if (!type) {
          return {}
        }
        const map: { [props: string]: object } = {
          Select: { style: {  '100%' } },
          default: {},
        }
        return map[type] || map.default
      }
    

    注意:当出现表单显示一个详情文字或者一个按钮 此时需要用isDetail干掉getFieldDecorator托管

    实现提交表单SubmitForm

     * @param form -`antd`的form
     * @param title - 主标题
     * @param subTitle - 分组标题
     * @param FormItemInfos - 二维数组 顺序和数量和分组标题对应 存放表单项信息
     * @param isLoading - `dva-loading` 执行effects过程中的loading
     * @param onSubmit - 传入的submit方法
     * @param buttonArea - 可选 不传默认数提交和取消 传入覆盖
     * @param children - 在表单下面 按钮区域上面的传入内容
     * @param formLayOutType - 布局方式 详情见`getLayOutByType`方法
     * @returns ReactNode
    ......
     <Form onSubmit={onSubmit}>
    
            <SubmitLayOut subTitle={subTitle} title={title} renderFormArea={renderFormArea}>
              {children}
              {buttonArea?buttonArea: <Row type="flex" gutter={24} justify="center">
                <Col>
                  <Button type="primary" htmlType="submit" loading={isLoading}>提交</Button>
                </Col>
                <Col>
                  <Button type="default" onClick={() => router.go(-1)}>取消</Button>
                </Col>
              </Row>}
            </SubmitLayOut>
    
        </Form>
    

    BaseForm的调用在renderFormArea方法里

    // 水平和垂直布局
    export enum FormLayOutType {
      normal = 'normal',
      vertical = 'vertical'
    }
    
    const renderFormArea = (idx: number) => {
        // 在这边加上normal表单的layout
        const FormItemInfo = FormItemInfos[idx]
      // 根据传入参数 返回布局类型
        const _FormItemInfo = getLayOutByType(formLayOutType||FormLayOutType.normal, FormItemInfo)
        return <BaseForm formItems={_FormItemInfo} form={form}/>
      }
    

    FormItemInfos这里是二维数组,二维分别承载分组信息和表单项信息 而且在这里用getLayOutByType封装常用的水平 垂直布局方式

    这里结合了我们业务里的表单布局 大标题 和小标题对表单区域进行分组,我将布局单独搞了个SubmitLayOut组件 将渲染逻辑和样式组件在其中实现 这样也方便更换成其他LayOut

    // SubmitLayOut
     * @param title - 主标题
     * @param subTitle - 分组标题
     * @param renderFormArea - 根据分组的顺序 渲染表单区域
     * @param children - 传入内容
    ......
      <Card title={title} bordered={false} className={styles["special-card"]}>
          <List itemLayout="vertical" className="special-list">
            {subTitle.map((item,idx) => {
              return (
                <>
                  <Meta title={item}/>
                  <List.Item>
                    {renderFormArea(idx)}
                  </List.Item>
                </>
              )
            })}
          </List>
          {children}
        </Card>
    
    
    

    结语

    后续还实现了serachForm等业务相关性高的组件,所以个人觉得封装的思路主要是

    • 底层组件越纯粹越好
    • 中层可以实现一些适用性比较高的具体业务组件和通用方法
    • 高层就具体要页面的细节的方方面面了
  • 相关阅读:
    .net 第一次请求比较慢
    配置文件读取与修改
    关系型数据库设计
    dynamic动态类型的扩展方法
    软件测试作业(三)
    软件测试作业(二)
    软件项目管理作业(二)
    软件项目管理作业(一)
    软件测试作业(一)
    C#最后一次作业(暂定)
  • 原文地址:https://www.cnblogs.com/amigod/p/antd-form-component-rebuild.html
Copyright © 2020-2023  润新知