结一

2019-03-31 12:03

React form 表单组件的解决方案

本文作者:IMWeb 结一 原文出处:IMWeb社区 未经同意,禁止转载

一直以来,表单对于前端来说都是一个不得不面对的坑。而对于设计一个表单组件来说,主要需要考虑以下两点:

  • 各个元素如何排版布局
  • 表单验证

各个元素如何排版布局

首先,整个表单可以分为多个表单项。而一个表单项从结构上可能会涉及到 6 个部分:label、前缀、表单元素(或自定义的表单元素)、后缀、说明文字,校验态。大概如下图:

大体 HTML 结构可以设计如下:

div.f-item
    label.f-label // 如果需要标注required,子元素还要加个required判断
    div.f-field
        div.f-element-wrap // 如果没有前后缀,可以不需要该层
            span.f-prefix // 前缀
            input.f-element // 输入框等表单元素
            span.f-suffix // 后缀
        p.f-description // 描述说明
        div.f-msg // 检验信息

当然还有一些非常简单的情况,不需要进行验证,而提示信息也可以使用 placeholder 来搞定,所以是不需要这么多层结构的,于是结构可以简化如下:

div.f-item
    label.f-label // 如果需要标注required,子元素还要加个required判断
    span.f-prefix // 前缀
    input.f-element // 输入框等表单元素
    span.f-suffix // 后缀

对于自定义的元素,替代掉 input.f-element 的位置就好。这样我们就可以设计出一个 FormItem 组件。该组件有大概如下几个属性:

  • simple:是否使用简化版的 HTML,默认为 false
  • className:自定义 class
  • name:表单项名称,用于表单元素的 name 属性,和 label 的 for 属性
  • label: 标签内容
  • isRequired:是否必须
  • prefix:前缀内容
  • type:单个输入框类型(email,areatext,password...),默认为 text
  • placeholder
  • suffix:后缀内容
  • des:描述说明
  • checkMsg:检验信息
  • value:值
  • onChange:值改变事件

除此之外,我们还可以将剩余属性全部透传给表单元素,如设置 focusblur 事件或设置 disabled 禁用等。

由于表单元素的复杂性,所以组件封装默认只处理一些 type 输入框类型的。对于非输入框类型的表单元素,统一使用 children 的形式来。

如下几种使用:

<FormItem name="username" label="用户名" value={} onChange={} />
<FormItem name="id" label="证件类型" />
    {/* 选择框,单选多选,多个元素,自定义组件等使用 children 形式 */}
    <select value={} onChange={}></select>
    <input type="text" value="" onChange={}>
</FormItem>
<FormItem name="comnent" type="areatext" label="申诉" value={} onChange={} />

这样 HTML 结构的基本架子已经搭建好了,现在需要考虑一些排版上的问题。

排版布局

对于第一种完善的 HTML 结构,由于标签留得比较足够,对于排版布局的变化只需要通过 class 去控制即可。而对于极简模式下的 HTML 结构,由于标签没有多余,所以在排版布局方面的变化没有那么灵活,不过既然是极简模式,想必也没有那么复杂。

标签与表单元素同行

这种情况属于多数情况,所以我们作为默认的效果。上面的效果图就是这种。

多个表单项同行显示

如下这种多个表单项同行显示的情况也是比较常见的,所以可以通过新增一个属性 inline 来控制,默认为false,设置为 true 即启用该效果。效果图如下:

标签单行显示

同样对于一些 label 标签需要单行显示的。也需要多加一个 vertical 属性来控制,默认为 false, 设置为 true 即启用该效果。效果图如下:

PS:由于该效果与上面的多个表单项同行显示属于可以共存的,所以需要两个属性来单独控制。

校验信息

同样默认的话,检验信息是放在表单元素的右侧,但是有些情况需要在表单元素的下方显示,所以新增一个属性 checkMsgShowBelow 来控制,同样也是默认为 false,设置为 true 即启用该效果,如下图:

除此之外,还有一个特例情况,它既不显示在表单元素的右边也不是下面。而是在其他地方进行提示。这时候就需要隐藏掉检验信息了,所以同样新增一个属性 checkMsgHide 来控制,如下图元素框显示错误态,但是提示信息放到其他地方显示:

最后得到 FormItem 的属性如下:

具体使用可查看 FormItem 组件 demo

表单验证

对于一个表单项 FormItem 组件来说,验证一般会涉及到三个属性valueonChangecheckMsg。如果一个表单中只有多个表单项,每个都会写一遍,实在是有点不怎么好看。

为了表现更优美点,所以设计这三个通用的检验属性由 Form 组件传入,然后通过 context 来绑定到 FormItem 组件,当然这样也方便后面的统一检验逻辑的处理等。

context

1、 创建 context

const FormContext = React.createContext({
  values: {},
  checkMsg: {},
  onChange: () => {},
});

2、创建 Form 组件,使用 Provider

const value = {
    values,
    checkMsg,
    onChange,
  };

  return (
    <FormContext.Provider value={value} >
      <form className={} {...rest} >
        {children}
      </form>
    </FormContext.Provider>
  );

3、创建高阶组件 FormItemContext,使用 Consumer,通过 name 属性,得到相应的各个表单项对应的valuecheckMsg。透传其他属性。

onChange 事件统一管理,默认将带有三个参数:name、value、event 对象

export function withFormContext(Component) {
  return (props) => {
    const { name } = props;

    return (
      <FormContext.Consumer>
        {({ values, checkMsg, onChange }) => {
          return (
            <Component value={values[name]} checkMsg={checkMsg[name]} onChange={onChange} {...props} />
          );
        }}
      </FormContext.Consumer>
    );
  };
}

const FormItemContext = withFormContext(FormItem);

export default FormItemContext;

这样对于需要验证的表单,就可以写成如下这样:

// state
this.state = {
    values: {
        username: 'xxx',
        email: 'xxx',
    },
    formErr: {},
}

// change 事件
handleChange = (name, value, e) => {
    // 用户名验证
    if (name === 'username') {}
    // 邮箱验证
    if (name === 'email') {}
}

// 其他事件,如 blur 事件,因为是透传的,所以没有任何参数提供
handleBlur = () => {}

<Form values={this.state.values} checkMsg={this.state.formErr} onChange={this.handleChange}>
    <FormItemContext label="用户名" name="username" onBLur={this.handleBlur} />
    <FormItemContext label="邮箱" type="email" name="email" />
</Form>

4、校验

检验将使用 schema-typed 这个数据建模及数据验证工具。整个校验设计非常赞,简直是眼前一亮,具体可以查看文档。

成果

最后 demo 来演示下最终的使用。如下一个表单:

  • 年龄为实时验证
  • 整个提交表单时候还会全部验证一遍

<iframe src="https://codesandbox.io/embed/j4nmm4p7kv?fontsize=14" title="form" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>

PS:如果上面的 iframe 标签嵌入的演示 demo 无效,大家可自行点击 src 中的路径进行查看效果。

最后奉上 NPM:react-form-next

0条评论

    您需要 注册 一个IMWeb账号或者 才能进行评论。