再再再重构一次表单组件

512 阅读4分钟

最近又重构了一次表单组件(react&&hook),力求使表单组件足够好用,并实现以下几个目标
看演示

  • 配置化: 模板less,配置化实现表单
  • 结构灵活: 除了表单本体之外,能够灵活的增加各种辅助结构
  • 模块化: 表单不仅仅能作为form的一个部分,也能够拆分出来独立使用
  • 动态化: 对表单可动态增减
  • 联动: 表单之间的联动可通过配置化实现
  • 可扩展: 表单需求千千万,当然必须要扩展

配置化

做模板less的好处是可以在JS就可以对逻辑,业务、渲染进行修改,在表单业务复杂的情况下,模板反而增加了很多不必要的工作量,最终希望模板仅仅只是一个标签引用

配置

const config = {
  data: [
    {input...},
    {button onClick=responseClick...}
  ]
}

function responseClick(){
  form.append({input...})
}

模板

<Form data={config} />

结构

单表单

一个完整的表单,除了表单本体,还有如出错信息,是否必选,表单标题等等属性需要一些辅助结构来帮助表达,如下图

上图只是一个简单的示意,应该还需要诸如password的小眼睛,下拉菜单的小箭头等等UI层面的辅助结构。诸如上图的这些辅助结构可以通过配置传入,有值时显示,无值时不显示,另外还需要配套api,按照需求调用它们

配置code

const input = {type: 'text', id: 'uniq-id', value: '你好', className: '...'}

复合表单

什么是复合表单,类似于省市区这样的表单就是,它由多个表单个体组合而成,并且表单需要支持各种类型, 如下图

配置code

const input = [
  {title: '用户名', type: 'text', id: 'uniq-id', value: '...', className: '...'},
  {title: '说明', type: 'text', id: 'uniq-id', value: '...', className: '...'}
]

组表单

多个复合表单或者单表单混合在一起构成,它是完整的表单的一个独立部分,多个组表单合并构成完整表单,如下图

配置code

const input = {
  title: '组表单',
  input: [
    {表单-1},
    {表单-2},
    {表单-3},
  ]
}

完整表单

由多个组表单构成完整表单,如下图

配置code

data: [
  // 表单组-1
  {
    title: '表单组1-标题', 
    input: [
      表单-1,
      表单-2
      ...
    ]
  },
  
  // 表单组-2
  {
    title: '表单组2-标题', 
    input: [
      表单-1,
      表单-2
      ...
    ]
  },
  
  ...
]

模块化

在有了各种结构的表单零件之后,通过配置我们可以组合或者单独使用这些零件(在不同的场景中)

单独使用

const config = {title: '单个表单', value: '...'} 
const Text = <form.Text data={config} />

组合使用

const config = [
  {
    title: '姓名',
    type: 'text',
    value: '...'
  },
  {
    title: '职业',
    type: 'text',
    value: '...'
  },
]
const group = <form.Group data={config} />

动态化

配置是死的,怎么配置就输出什么样的结构,但并不能满足产品需求。很多场景需要动态增减表单,并且还要保持原有填写的数据。以组表单为例

const config = [
  {
    id: 'id-1',
    title: '姓名',
    type: 'text',
    value: '...'
  },
  {
    id: 'id-2',
    title: '职业',
    type: 'text',
    value: '...'
  },
]
const groupJSX = <form.Group gid='g-1' data={config} />
const group = form.getGroup('g-1')

append 追加配置

const newConfig = {
  id: 'id-new',
  title: '...',
  type: '',
  value: ''
}
group.append(newConfig)

delete 删除配置

group.delete({id: 'id-2'})

insert 插入配置

group.insert(1, {title: '插入表单', type: '', value: ''})

以上只是举例列出相关的api方法,使用这些api方法我们能够对表单做灵活的修改

联动

很多场景中表单之间需要联动,可以通过配置来实现联动操作,引入一个联动属性union建立观察者与被观察者的关系

const textConfig = {id: 'id-1', title: '...', type: 'text', value: '...'}
const textWatcher = {
  title: '观察者表单',
  id: 'watcher',
  type: 'text',
  value: '...', 
  union: [
  	'id-1',
    'onChange',
    function(value){
      /* do something */
    }
  ]
}

union属性,包含观察对象,观察对象方法名,响应方法,通过union属性建立了不同表单之间的关系, 当id-1表单值change时将触发观察者的响应方法

可扩展

表单组件可以通过register方法注册新的表单,以适应不同的表单交互需求,比如下例

// cell默认支持text表单(部分)
// 注册一个自定义text表单,替换内置text表单,拥有更丰富的表单类型
function TextInput(props) {
  let $props = { ...props };
  $props.store = null;
  $props.registeroptions = null;
  return <input {...$props} />;
}

register(
  "Text",
  [
    "text",
    "number",
    "telephone",
    "password",
    "color",
    "email",
    "date",
    "time",
    "range",
    "month"
  ],
  TextInput
);

const textConfig = {type: 'month'}
const form = <form.Text data={textConfig} />

上例定义了一个text类型的表单,当type类型落在自定义表单的范围内,就会使用预定的react方法组件来输出结构,我们还可以为该方法组件定义一些api方法,以灵活控制表单

写在最后

经过一个多月陆陆续续的开发,已完成一个版本的表单组件,基本实现上述功能

-->一款基于react/hook的表单组件