JSON生成Form表单的方法示例


Posted in Javascript onNovember 21, 2018

JSON表单描述

JSON表单是一个基于React的抽象组件,它可以把JSON数据格式描述的表单转换成项目中的表单,它可以用简短的几行代码,快速的生成Form表单。

JSON表单的优点是:

  • 可以快速构建出一个表单
  • 表单的数据、逻辑、视图分离,方便抽离和进一步抽象
  • 提供校验、自动缓存等额外功能,提升录入体验
  • 可以跨项目的共用复杂的表单组件

原始表单的缺点

1:代码量庞大,开发效率低
每次开发一个表单页的时候,都需要重复编写表单组件及其交互事件的代码,这块代码重复编写且与主线业务逻辑无关,除此之外,表单的校验、缓存等额外功能,也需要不少的代码量,这样就造成了一个表单页的代码量庞大。

2:不便于抽离和进一步的抽象
在一个表单页内,往往会将表单数据、表单组件、控制逻辑杂糅在一起,当发现某一个子功能在很多场景下都需要用到的时候,想把该子功能拆分出来发现并不容易,因为逻辑、数据、视图的杂糅,导致想把子功能不受影响的剔除出来需要很仔细的检查,这样就导致功能的抽离和抽象的不便

3:维护成本高
这个和第二个问题是同样的原因,这也是我的亲身经历,当我在项目中想优化一个小功能的时候,发现不仅把之前的逻辑改没了,还引出了不少的bug,这导致在一个逻辑很复杂的表单里,维护变成了一件高危工作。

4:需要额外处理校验和缓存等功能

一个栗子

const config = {
  formKey: 'example-form',
  data: {
    name: '',
    descr: '',
    typeName: ''
  },
  config: [
    {
      type: 'input',
      dataKey: 'name',
      label: 'param',
      placeholder: '请输入param',
      validate: ['required', /^[a-zA-Z_{}0-9]+$/g],
      style: {
        display: 'inline-block',
        width: 270,
      },
    },
    {
      type: 'select',
      dataKey: 'typeName',
      options: ['string', 'integer', 'float'],
      style: {
        display: 'inline-block',
        width: 100,
        margin: '0 15px'
      },
      validate: [{type: 'required', message: 'param类型不能为空'}]
    },
    {
      type: 'textarea',
      dataKey: 'descr',
      placeholder: '请输入param含义',
      label: 'param含义',
      validate: ['required'],
      style: {
        width: 385,
      }
    },
  ]
}

<From ref={ref => this.FormWrap = ref} config={config}></From>

上面是用JSON描述的三个常用的表单组件组合成的表单,其效果图如下:

JSON生成Form表单的方法示例

JSON表单的格式

{
  formKey: 'paramAddForm',
  data: {},
  config: []
}

属性 是否必传 说明 类型 默认值
formKey 用来自动缓存,localStorage的key,不传表示不自动缓存 string -
className 用来添加一些自定义样式 string -
data 表单的提交数据,有自动缓存和校验功能 object -
assisData 用于表单控制逻辑的额外数据 object -
config 组件配置,表单组件的配置 Array -
realTimeSubmit 表单是否实时提交,一般用于筛选表单 boolean false

表单组件的配置

{
  type: 'input',
  dataKey: 'name',
  label: 'param',
  validate: ['required'],
  style: {}
}

属性 是否必传 说明 类型 默认值
type 表单组件的类型,其值可以为: input、select、textarea、form_array、container和一些自定义表单组件 string -
dataKey 指定表单组件值的key,可以为param.name.firstName形式 string -
label 表单组件的label string -
placeholder 表单组件的placeholder string -
validate 表单组件的校验规则 Array -
style 表单组件的布局样式 string -
options 当表单组件为select时,需要传入的options Array -
render 当type为container时,为自定义组件,render为渲染方法 Function -
preventSubmit 当realTimeSubmit为true时,控制当前表单组件是否实时提交 boolean false
children 当type为form_array时,children表示子组件配置列表 Array -
modifyDataFn 当type为自定义组件时,且需要覆盖render方法中的提交数据方法,可以使用modifyDataFn来重新自定义提交数据 Function -

关键字段解释

1. type

type是用来唯一表示表单组件类型的字段,其中JSON表单提供了三种默认的表单组件:input、select、textarea,还有两种复杂类型的表单组件:form_array、container。

form_array表单组件表示其数据结构为Array,含有增加项删除项的复合表单组件,该表单组件的配置里多一个children的字段,里面是每一项里面的表单组件配置的集合,其表单组件的效果如下图所示:

JSON生成Form表单的方法示例

container是用来自定义表单的接口,具体用法参考下面具体的介绍。

2. validate

validate是校验表单组件数据正确性的字段,其值为数组,里面的数组元素可以为String、object、RegExp、Function。

JSON表单采用的是async-validator异步处理校验,在JSON表单内部会将validate传入的校验关键字解析为async-validator的rules。所以validate数组元素如果为object的话,其内容就是async-validator的rules。

1. 数组元素为string,其值可以为:

  • string,值必须为string
  • number,值必须为数字
  • required,值不能为空
  • boolean,值必须为布尔值
  • integer,值必须为整数形
  • float,值必须为浮点型
  • email,值必须为邮箱类型

2. 数组元素为object,其值为rules:
{type: 'enum', enum: ['1', '2'], message: '值不在确定范围内'}

3. 数组元素为RegExp, validate: [/^[a-zA-Z_{}0-9]+$/g]

4. 数组元素为Function, validate: [ (rules, value, callback) => {}]

3. style

用来确定表单组件在表单内的布局样式,比如想让表单组件行内显示,且宽度为200,其style值如下:

{
  display: 'inline-block',
  width: 200
}

container表单组件

container表单组件是用来自定义表单组件的,它主要的作用有以下几点:

  • 添加表单组件库,例如Ant-Design
  • 自定义表单组件,例如图片上传组件
  • 用来构建复杂的表单组件
  • 处理控制逻辑和联动逻辑

使用栗子

import { Input, Select } from 'antd'
const Option = Select.Option
{
  type: 'container',
  dataKey: 'descr',
  style: {
    display: 'inline-block',
    width: 100,
    margin: '0 15px'
  },
  options: ['string', 'integer', 'float'],
  render: (curData, config, {changeFn, getFocus, loseFocus, error}) => {
    return <Select value={curData} 
      style={{width: '100%', height: 35}} 
      onMouseEnter={getFocus}
      onChange={(value) => changeFn(value, () => {
        loseFocus()
      })}>
      {
        config.options && config.options.map((item, idx) => <Option key={idx} value={item}>{item}</Option>)
      }
    </Select>
  }
},

container表单组件只是多一个render渲染方法,里面可以自定义表单组件的渲染内容,render方法提供如下参数:

1. curData: 当前container组件的值,跟dataKey相关
2. config: 当前container组件的配置
3:{changeFn, changeDataFn, getFocus, loseFocus, error, JSONForm}
changeFn, changDataFn是提交数据的方法,changeFn只能修改当前组件dataKey的值,changeDataFn可以修改data中任意字段的值,changeFn(value, [callback]), changeFn(dataKey, value, [callback])
getFocus,loseFocus是自定义处理校验的字段,loseFocus是开始校验,getFocus是去掉校验的报错信息
error是校验结果的报错信息
JSONForm是在container中使用JSON表单的组件配置用来生成新的表单组件,意思里container中依然可以嵌套表单组件。

使用antd的组件库

JOSN表单只提供了input、select、textarea三种默认的表单组件,远远不够真实的项目中使用,所以我们可以将antd组件库中的组件封装到JSON表单中,这样我们就可以再项目中很快的使用antd中的组件。

antd-components.js

import React from 'react'
import { Input } from 'antd'
export default [
  {
    type: 'antd-input',
    render: (curData, config, {changeFn, getFocus, loseFocus, error}) => {
      return <Input value={curData} 
        onFocus={getFocus}
        onBlur={loseFocus}
        placeholder={config.placeholder ? config.placeholder : ''}
        style={{borderColor: !!error ? '#f5222d' : ''}}
        onChange={event => changeFn(event.target.value)} />
    }
  }
]

我们在antd-components.js文件中声明一个antd-input的自定义组件,然后在JSON表单中引入该自定义表单组件:

init.js

import Form from 'Form'
import components from './antd-components'
From.createCustomComp(components)

const config = {
  formKey: 'paramAddFromAntd',
  data: {
    name: '',
  },
  config: [
    {
      type: 'antd-input',
      dataKey: 'name',
      label: 'Param',
      placeholder: '请输入param',
      validate: ['required', /^[a-zA-Z_{}0-9]+$/g]
    }
  ]
}
<From ref={ref => this.FormWrap = ref} config={config}></From>

使用container来引入antd组件库,其原理就是通过container将antd组件封装成'antd-input'自定义组件,然后使用它,这种方式不仅可以用来封装组件库,还可以用来共享一些共用表单组件,可以将常用的复杂表单组件封装在一个共用文件里,然后在不同项目中引用,就可以跨项目共用表单组件。

在自定义组件中,如果需要自定义表单提交数据函数,但是又不能重写render方法以防覆盖原先的render方法,所以可以使用modifyDataFn方法来覆盖render中的提交数据部分。

modifyDataFn: ({changeFn, changeDataFn}, {parent, self}) => {
  let {parentData} = parent
  parentData = parentData.map(item => ({
    ...item,
    name: self.curData
  }))
  changeDataFn(parent.parentKey, parentData)
}

处理控制逻辑和联动逻辑

在JSON表单JSON配置中,有assistData的选填字段,该字段为JSON表单处理控制逻辑的额外数据,例如在表单内有一个刷新按钮,其实现代码如下:

{
  data: {},
  assistData: {
    refreshParam: false
  },
  config: [
    {
      type: 'container',
      dataKey: 'assistData.refreshParam',
      render: (curData, config, {changeFn, changeDataFn}) => {
        const handleClick = () => {
          changeDataFn('assistData.refreshParam' ,true)
          setTimeout(() => {
            changeDataFn('assistData.refreshParam' ,false)
          }, 1000 * 3)
        }
        return <React.Fragment>
          {
            config.index === config.parentData.length - 1 &&
            <Popover placement="top" content="刷新param列表">
              <Button shape="circle" loading={curData} onClick={handleClick}>{!curData && <Icon type="reload" />}</Button>
            </Popover>
          }
        </React.Fragment>
      }
    },
  ]
}

注意: 如果要使用assistData中的数据,其dataKey必须以assistData开头,且必须使用changeDataFn自定义提交assistData数据。

render方法内嵌套组件配置

{
  type: 'container',
  dataKey: 'param',
  render: (curData, config, {changeFn, changeDataFn, JSONForm}) => {
    return <div>
      {
        JSONForm([
          {
            type: 'input',
            dataKey: 'name',
            placeholder: '请输入param',
            validate: ['required'],
          }
        ])
      }
    </div>
  }

这样就可以在container内嵌套组件配置,实现更复杂的表单组件。

JSON表单提交数据

非实时表单提交

非实时表单提交数据,就是在表单输入完毕后,点击提交按钮统一提交所有的数据,其提交的方式如下:

function handleClick() {
  this.FormRefs.getValue((valid, data) => {
    // valid 表示校验结果,false表示校验不通过
  })
}

实时表单提交

实时表单的提交首先需要注册提交函数:

componentDidMount(){
  this.FormRefs.registerSubmit((valid, data) => {
    console.log(valid, data)
  })
}

接着在配置里设置允许实时提交的字段:

{
  formKey: '',
  realTimeSubmit: true
}

如果需要在某些表单组件里自定义是否实时提交,需要在组件配置里设置阻止实时提交字段为true:

{
  dataKey: '',
  preventSubmit: true
}

JSON表单的应用场景

表单分类

a. 按复杂度分类
1. 简单表单:表单组件为input、select、textarea等常见的几种,且表单组件之间逻辑独立
2. 复杂表单:表单组件内容和交互复杂且相互之间存在复杂的逻辑

其中复杂表单又可以分为:
1. 联动表单,上一个表单组件会影响接下来表单的值
2. 实时表单,表单组件的事件会触发表单的实时提交,例如筛选表单
3. 富控制表单,表单内部含有很多的控制逻辑

JSON表单最适合的应用场景是简单表单,它可以用极少的代码,快速的构建出表单来,对于复杂类型的表单,JSON表单需要使用container来构建复杂的表单组件、处理复杂的控制逻辑,其代码量优势虽然并不明显,但是JSON表单可以使其代码清晰,将表单组件和表单逻辑彻底解耦,便于抽离和维护,便于共享常用组件,也带来不少的好处。

到目前为止,JSON表单适合大部分的表单应用场景。

JSON表单解决的问题

  • 减少了表单代码量,不需要重复的开发表单组件,只需要输入组件配置即可
  • 将表单组件和数据解耦,便于子功能的拆分和常用组件的共享
  • 简化了校验功能,只需要传入validate字段即可
  • 添加了自动缓存功能

在我的项目,我尝试了使用原始表单和JSON表单两种方式来实现同一个表单页,原始表单我编写了600多行的代码,而在JSON表单中,只有不到150行。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Javascript学习笔记5 类和对象
Jan 11 Javascript
JQuery中serialize()、serializeArray()和param()方法示例介绍
Jul 31 Javascript
Javascript前端UI框架Kit使用指南之Kitjs简介
Nov 28 Javascript
Jquery技巧(必须掌握)
Mar 16 Javascript
jQuery实现table中两列CheckBox只能选中一个的示例
Sep 22 jQuery
vue2实现可复用的轮播图carousel组件详解
Nov 27 Javascript
vue监听对象及对象属性问题
Aug 20 Javascript
Vux+Axios拦截器增加loading的问题及实现方法
Nov 08 Javascript
判断文字超过2行添加展开按钮,未超过则不显示,溢出部分显示省略号
Apr 28 Javascript
DatePickerDialog 自定义样式及使用全解
Jul 09 Javascript
Vue.js watch监视属性知识点总结
Nov 11 Javascript
JavaScript事件冒泡机制原理实例解析
Jan 14 Javascript
apicloud拉起小程序并传递参数的方法示例
Nov 21 #Javascript
vue中组件的过渡动画及实现代码
Nov 21 #Javascript
详解promise.then,process.nextTick, setTimeout 以及 setImmediate的执行顺序
Nov 21 #Javascript
Nuxt.js之自动路由原理的实现方法
Nov 21 #Javascript
nuxt.js中间件实现拦截权限判断的方法
Nov 21 #Javascript
Nuxt.js SSR与权限验证的实现
Nov 21 #Javascript
详解nuxt路由鉴权(express模板)
Nov 21 #Javascript
You might like
微信利用PHP创建自定义菜单的方法
2016/08/01 PHP
如何使用PHP给图片加水印
2016/10/12 PHP
PHP实现二维数组去重功能示例
2017/01/12 PHP
PHP让网站移动访问更加友好方法
2019/02/14 PHP
jQuery lazyLoad图片延迟加载插件的优化改造方法分享
2013/08/13 Javascript
跟我学习javascript的call(),apply(),bind()与回调
2015/11/16 Javascript
vue-cli3.0配置及使用注意事项详解
2018/09/05 Javascript
vue实现微信分享功能
2018/11/28 Javascript
微信小程序云开发使用方法新手初体验
2019/05/16 Javascript
浅谈VUE防抖与节流的最佳解决方案(函数式组件)
2019/05/22 Javascript
小谈angular ng deploy的实现
2020/04/07 Javascript
vue实现简单全选和反选功能
2020/09/15 Javascript
python中的__slots__使用示例
2015/02/26 Python
Django自定义过滤器定义与用法示例
2018/03/22 Python
python networkx 包绘制复杂网络关系图的实现
2019/07/10 Python
Django框架组成结构、基本概念与文件功能分析
2019/07/30 Python
解决Tensorflow 使用时cpu编译不支持警告的问题
2020/02/03 Python
python实现IOU计算案例
2020/04/12 Python
python实现图片转换成素描和漫画格式
2020/08/19 Python
设计师珠宝:Ylang 23
2018/05/11 全球购物
Farnell德国:电子元器件供应商
2018/07/10 全球购物
植村秀加拿大官网:Shu Uemura加拿大
2019/09/03 全球购物
Fnac西班牙官网:法国文化和电子产品零售商
2021/03/14 全球购物
美国知名眼镜网站:Target Optical
2020/04/04 全球购物
高中自我评价分享
2013/12/05 职场文书
安全教育心得体会
2013/12/29 职场文书
竞争上岗实施方案
2014/03/21 职场文书
2014年新生军训方案
2014/05/01 职场文书
钳工实训报告总结
2014/11/04 职场文书
个人先进事迹材料
2014/12/29 职场文书
装修公司管理制度
2015/08/05 职场文书
小学一年级语文教学反思
2016/03/03 职场文书
创业计划书之小型广告公司
2019/10/22 职场文书
Python绘制分类图的方法
2021/04/20 Python
Spring Boot项目传参校验的最佳实践指南
2022/04/05 Java/Android
配置nginx负载均衡
2022/05/06 Servers