React-UI 0.6 Form设计 | 少将全栈
  • 欢迎访问少将全栈,学会感恩,乐于付出,珍惜缘份,成就彼此、推荐使用最新版火狐浏览器和Chrome浏览器访问本网站。
  • 吐槽,投稿,删稿,交个朋友
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏少将全栈吧

React-UI 0.6 Form设计

点滴 admin 8年前 (2016-10-12) 2078次浏览 已收录 扫描二维码

        最近研究动态表单,分享下如下Form设计。

React-UI 0.6 Form设计

0.6版最大的变化,把整个Form架构重新写了一遍,差不多覆盖了整个UI库的2/3。

0.6之前的Form,是这样一个结构。

React-UI 0.6 Form设计

使用的时候是这样的

<Form layout="aligned" onSubmit={...} data={...}>
  <FormControl name="text" label="text" type="text" min={2} max={6} />
  <FormControl name="email" label="email" type="email">
    <span className="rct-input-group">
      <span className="addon"><Icon icon="email" /></span>
      <Input type="email" />
    </span>
  </FormControl>
  ...
</Form> 

这样设计是想把所有的表单组件注册到FormControl,由FormControl实现validate,生成tip文字,和Form交互的工作。不过由于写这个UI库的时候,接触React时间还不长,有些思想还是停留在Angularjs和Vue的双向绑定上,另外也没有想到好的办法解决获取Form表单数据的问题。所以给所有的表单组件加上了getValue和setValue两个方法进行传值。

这个设计实现了我开始想实现的大部分功能,但是很快发现不太够用了。很多表单都会有一两个比较复杂的交互,单纯通过FormControl的props很难描述,通过使用children这样的方式,也非常吃力,因为一个FormControl只能描述一个FormData字段,如果一行有多个表单组件,就不好用了。另外,由于Form内使用了cloneElement,在最外层也无法拿到原始的ref。

最致命的是,在使用mixin的时候,使用getValue和setValue不会有多大的问题。但是,后来大部分组件都采用了higherorder component,来附加一些通用的功能,比如取服务端数据用的Fetch。这样FormControl就无法直接通过setValue和getValue来操作数据了。虽然可以通过改变higherorder来支持,但是这样显然是不对的。

所以,在0.6的时候,决心对Form进行一次重构。新的架构是这样的。

React-UI 0.6 Form设计

最大的变化是增加了两个FormItem,一个是higherorder的组件,用来包装input类的表单组件,实现了原来FormControl的validate的功能。另外一个是对这个higherorder组件的封装,暴露出来供使用者调用。

原先的FormControl职责变得简单一些,不再处理数据,只用来生成label文字,汇总FormItem子组件(一个FormControl下可以有多个FormItem)的状态,生成相应的提示/错误信息。

最困难的是Form的部分。数据向下传递比较简单,要在onSubmit的时候获取到所有表单项的状态:是否通过校验,这个比较麻烦。在一个FormItem没有触发onChange的情况下,Form如何知道这个FormItem存在,并让它去做validate校验?如果通过了校验,怎么拿到初始值?

想了好几种解决方案,都不太理想。最终决定,增加了一个itemBind机制。

 this.itemBind = (item) => {
      this.items[item.id] =item;

      let data = this.state.data;
      data[item.name] = item.value;
      this.setState({ data });

      // bind triger item
      if (item.valiBind) {
        item.valiBind.forEach((vb) => {
          this.validationPools[vb] = (this.validationPools[vb] || []).concat(item.validate);
        });
      }
    };

    this.itemUnbind = (id, name) => {
      let data = this.state.data;
      delete this.items[id];
      delete data[name];
      delete this.validationPools[name];
      this.setState({ data });
    };

    this.itemChange = (id, value, err) => {
      let data = this.state.data;
      const name = this.items[id].name;

      if (data[name] !== value) {
        data[name] = value;
        this.setState({ data });
      }

      let valiBind = this.validationPools[name];
      if (valiBind) {
        valiBind.forEach((validate) => {
          if (validate) {
            validate();
          }
        });
      }

      this.items[id].$validation = err;
    };
  } 

每个FormItem初始化的时候直接bind到Form的items,数据改变的时候,通过itemChange方法,直接把数据和状态提交到Form的formData。Form在submit的时候,检查items里面每个item是否通过validate,如果没有执行过validate,通知item执行。如果所有items状态都ok,再触发onSubmit。

这一切对于使用者来说是黑盒的。对外的Api基本上是没有变的。

一个完整的Form调用现在是这样。

<Form data={...} onSubmit={...}>
  <FormControl label="label">
    原生input:
    <FormItem name="filed1" value="初始值" min={2} max={12} validator={...}>
      <input />
    </FormItem>
    自定义组件,需要支持value传入值,onChange传出值
    <FormItem>
      <CustomComponent {...} />
    </FormItem>
    Input组件
    <Input name="filed2" type="url" />
  </FormControl>
</Form> 

如果FormControl只有一个组件,可以写成这样,这样就和之前的版本没有区别了。

<Form data={...} onSubmit={...} layout="...">
  <FormControl label="label" name="filed" type="text" required min={2} max={12} validator={...} />
</Form> 

demo见这里

动态表单

这个是一直想实现的功能,0.6里也顺带完成了,通过一个json格式,动态生成一个表单。

<Form button="确定" fetch={’json/form.json’} controls={[
  { name: ’text’, type: ’text’, min: 3, max: 12, label: ’text’, grid: 1/3 },
  { name: ’datetime’, required: true, type: ’datetime’, label: ’datetime’, tip: ’自定义tip文字’ },
  { label: ’two items’, items: [
    { name: ’startTime’, type: ’date’ },
    ’-’,
    { name: ’endTime’, type: ’date’ }
  ] },
  {
    name: ’select’, type: ’select’, label: ’select’, grid: 1/2, fetch: {url:"json/countries.json", cache:3600},
    mult: true, filterAble: true, valueTpl: "{en}",
    optionTpl: ’<img src="//lobos.github.io/react-ui/images/flags/{code}.png" /> {country}-{en}’
  }
]} /> 

实现这一步比较简单了,因为原本Form所有的children都是FormControl,只要把controls遍历一下,return <FormControl {…control} />就好了

在线的示例

来自: https://github.com/Lobos/react-ui/issues/43

喜欢 (0)
[🍬谢谢你请我吃糖果🍬🍬~]
分享 (0)
关于作者:
少将,关注Web全栈开发、项目管理,持续不断的学习、努力成为一个更棒的开发,做最好的自己,让世界因你不同。