chenxd1996

14 天前

实现一个简单的表单校验器

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

实现一个简单的表单校验器

1. 问题提出:

最近笔者在用React+antd做管理后台系统需求的时候,碰到了一个问题,就是在同一个antd的FormItem下面有多个子数据,那么在表单校验的时候某个数据一旦出错,整个FormItem下面的表单组件都会标红,无法准确标出出错的字段。

如图所示,这里的表格数据,其实都是同一个数据字段的子字段。可以看到,即使只有第一个input框校验出错,也会出现一个大红框,出错信息也是显示在整个表格下方,很难看到具体出错的位置。

我们的目标效果应该是这样的:

2. 解决方法:

Form表单下面是不能嵌套Form表单的,所以笔者试着自己写了一个简单的表单校验器。虽然有点简陋,但感觉也还有点意思,与大家分享一下。

首先能想到的是模仿getFieldDecorator,提供一个函数getField,调用getField(option)(formComponent)得到一个包装过的Component,在原来表单组件上加入错误信息显示。

例如:

getField({
    field: 'name', // field相当于是该字段的id,支持类似'userInfo.name', 'users[0].userInfo.name'
    validator: (value, values, callback) {
        // value为该字段的值
        // values为该字段的父字段的值。如果value是userInfo.name的值, 那么values就是userInfo
        // callback()时候为校验成功,callback('some error msg') 为检验失败
    }
})

好了,上面就是实现目标了,接下来开始一步步实现。

首先,肯定是要有一个容器用来存放校验器的,getField这个方法就是为了存放校验器,这个容器还要暴露出一个validate方法,这个方法一旦被调用,所有的校验器就都被调用,如果出错就会显示错误信息。

这个容器可以用class来实现,其大概内容应该如下:

class MyValidation {
  toValidate = {}; // 用来存放校验器的,key是field,value是validator

  constructor(context) {
    // context是表单所在Component,用来更新视图用的
    this.context = context
  }
  add(field, validator) { // 用来添加校验器的
  }

  @autobind
  getField(options = {}) {
   // 调用add函数保存校验器,并返回一个包装过element
   // 包装element也叫高阶组件,目的为了在原有组件下面显示出错信息
  }

  @autobind
  validate(values) {
    // 用来触发校验,values是表单数据      
  }

  @autobind
  clearFields() {
    // 用来清除校验信息
  }
}

看到这里,聪明的你也许已经看穿了一切了,如果有兴趣,可以照着这个思路,自己把细节实现一下。

接下来,我们将继续将探究一下每个函数的实现细节。

add

这个函数非常简单,如下:

   add(field, validator) { // 用来添加校验器的
       this.toValidate[field] = {
         validator,
       };    
   }

getField

这个函数略有些复杂,需要对React高阶组件有一定的了解

   getField(options = {}) {
      // 调用add函数保存校验器,并返回一个包装过element
      // 包装element也叫高阶组件,目的为了在原有组件下面显示出错信息
       const { field, validator } = options;
       this.add(field, validator); // 保存校验器
       return (Cmp) => { // Cmp就是将被包装的表单组件,例如<Input />
         return function (props) { // 函数式组件,因为不需要state
           Cmp.props = { ...Cmp.props, ...props }; // 将props传给Cmp
           const msg = errorRecord[field]; // errorRecord是全局变量,存放错误信息,看一下是否有错误信息
           return (
             <div className={msg ? 'has-error' : null}>
               {Cmp}
               {
                 <div className="ant-form-explain" style={errorMsgStyle}>
                   <span>{msg}</span>
                 </div>
                 }
             </div>
           );
         };
       };
   }

这里需要说明的是,errorRecord是个全局变量,这里不是通过高阶组件的setState来更新视图的,后面会讲到校验后如何触发视图更新。这里我没有自己写的样式,是直接用的antd表单校验的样式。

validate

   validate(values) {
       // 用来触发校验,values是表单数据
       // context是表单所在Component,用来更新视图用的
       const fields = Object.keys(this.toValidate);
       let isValid = true;
       fields.forEach((field) => {
         const { validator } = this.toValidate[field];
         const value = _.get(values, field); // 这里的_是lodash库,用它的get方法,获取对象的值,非常强大
         const callbackCreator = (field) => {
            return (msg) => { // 这里用了一个闭包,生成field的对应callback
              errorRecord[field] = msg;
            };
         };
         if (validator) {
           validator(value, values, callbackCreator(field));
         }
         const msg = errorRecord[field];
         if (msg) { // 如果有msg,说明出错了
           isValid = false;
         }
       });
       this.context.forceUpdate(); // 这里要更新下视图
       return isValid;     
   }

clearFields

   clearFields() {
      // 用来清除校验信息
      errorRecord = {};
      this.context.forceUpdate();
   }

大工告成,接下来看看如何使用吧,使用方法大概如下:

const validatation = new myValidation(this);
const { getField } = validation;

function () {

}

class TestCmp extends React.Component {
    getFields = () => {
        const Wrapper = getField({
            field: 'name',
            validator: (value, values, callback) => {
                if (!value || value === '') {
                    callback('名字不能为空');
                    return;
                }
                callback();
            }
        })(<Input />);
        return <Wrapper />
    }
    submit = () => {
        const isValid = validation.validate(this.props.form.getFieldsValue());
        if (!isValid) {
            return;        
        }
        // submit form data
    }
    render() {
        return (
            <Form>
                <FormItem>
                {
                this.getFields();
                }
                </FormItem>
            </Form>
            <Button onClick={this.submit}>
        )
    }
}

3. 总结

有时候简单的代码写多了感觉会比较平淡,遇到一些稍有难度问题,反而能让你学到更多的东西。笔者学习React不足两个月,可能在很多方面还是理解得不够到位,如有纰漏,欢迎读者批评指正,谢谢!

0条评论

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