使用async-validator编写Form组件的方法


Posted in Javascript onJanuary 10, 2018

前端开发中,表单的校验一个很常见的功能,一些 ui 库例如ant.design 与Element ui 都实现了有校验功能的 Form 组件。async-validator 是一个可以对数据进行异步校验的库,ant.design 与 Element ui 的 Form 组件都使用了 async-validator。本文就简单介绍一下 async-validator 的基本用法以及使用该库实现一个简单的有校验功能的 Form 组件。

1. async-validator 的基本用法

async-validator 的功能是校验数据是否合法,并且根据校验规则给出提示信息。

下面演示一下 async-validator 的最基本用法。

import AsyncValidator from 'async-validator'
// 校验规则
const descriptor = {
 username: [
 {
  required: true,
  message: '请填写用户名'
 },
 {
  min: 3,
  max: 10,
  message: '用户名长度为3-10'
 }
 ]
}
// 根据校验规则构造一个 validator
const validator = new AsyncValidator(descriptor)
const data = {
 username: 'username'
}
validator.validate(model, (errors, fields) => {
 console.log(errors)
})

当数据不符合校验规则时,在 validator.validate 的回调函数中,就可以得到相应的错误信息。

当 async-validator 中常见的校验规则无法满足需求时,我们可以编写自定义的校验函数来校验数据。一个简单的校验函数如下。

function validateData (rule, value, callback) {
 let err
 if (value === 'xxxx') {
  err = '不符合规范'
 }
 callback(err)
}
const descriptor = {
 complex: [
  {
  validator: validateData
  }
 ]
}
const validator = new AsyncValidator(descriptor)

async-validator 支持对数据异步校验,所以在编写自定义校验函数时,不管校验是否通过,校验函数中的 callback 都要调用。

2. 编写 Form 组件与 FormItem 组件

现在知道了 async-validator 的使用方法,如何将这个库跟要编写的 Form 组件结合起来呢。

实现思路

用一张图描述一下实现思路。

使用async-validator编写Form组件的方法

Form 组件

Form 组件应该是一个容器,里面包含不定数量的 FormItem 或者其他元素。可以使用 Vue 内置的slot 组件来代表 Form 里面的内容。

Form 组件还需要知道包含了多少个需要校验的 FormItem 组件。一般情况下,父子组件的通信 是通过在子组件上绑定事件实现的,但是这里使用 slot,无法监听到子组件的事件。这里可以在 Form 组件上通过$on 监听事件,FormItem 挂载或者销毁前触发 Form 组件的自定义事件即可。

按照这个思路,我们先编写 Form 组件。

<template>
 <form class="v-form">
  <slot></slot>
 </form> 
</template>
<script>
import AsyncValidator from 'async-validator'
export default {
 name: 'v-form',
 componentName: 'VForm', // 通过 $options.componentName 来找 form 组件
 data () {
  return {
   fields: [], // field: {prop, el},保存 FormItem 的信息。
   formError: {}
  }
 },
 computed: {
  formRules () {
   const descriptor = {}
   this.fields.forEach(({prop}) => {
    if (!Array.isArray(this.rules[prop])) {
     console.warn(`prop 为 ${prop} 的 FormItem 校验规则不存在或者其值不是数组`)
     descriptor[prop] = [{ required: true }]
     return
    }
    descriptor[prop] = this.rules[prop]
   })
   return descriptor
  },
  formValues () {
   return this.fields.reduce((data, {prop}) => {
    data[prop] = this.model[prop]
    return data
   }, {})
  }
 },
 methods: {
  validate (callback) {
   const validator = new AsyncValidator(this.formRules)
   validator.validate(this.formValues, (errors) => {
    let formError = {}
    if (errors && errors.length) {
     errors.forEach(({message, field}) => {
      formError[field] = message
     })
    } else {
     formError = {}
    }
    this.formError = formError
    // 让错误信息的顺序与表单组件的顺序相同
    const errInfo = []
    this.fields.forEach(({prop, el}, index) => {
     if (formError[prop]) {
      errInfo.push(formError[prop])
     }
    })
    callback(errInfo)
   })
  }
 },
 props: {
  model: Object,
  rules: Object
 },
 created () {
  this.$on('form.addField', (field) => {
   if (field) {
    this.fields = [...this.fields, field]
   }
  })
  this.$on('form.removeField', (field) => {
   if (field) {
    this.fields = this.fields.filter(({prop}) => prop !== field.prop)
   }
  })
 }
}
</script>

FormItem 组件

FormItem 组件就简单很多,首先要向上找到包含它的 Form 组件。接下来就可以根据 formError 计算出对应的错误信息。

<template>
 <div class="form-item">
  <label :for="prop" class="form-item-label" v-if="label">
   {{ label }}
  </label>
  <div class="form-item-content">
   <slot></slot>
  </div>
 </div>
</template>
<script>
export default {
 name: 'form-item',
 computed: {
  form () {
   let parent = this.$parent
   while (parent.$options.componentName !== 'VForm') {
    parent = parent.$parent
   }
   return parent
  },
  fieldError () {
   if (!this.prop) {
    return ''
   }
   const formError = this.form.formError
   return formError[this.prop] || ''
  }
 },
 props: {
  prop: String,
  label: String
 }
}
</script>

FormItem 在 mounted 与 beforeDestroy 钩子中还需要触发 Form 组件的一些自定义事件。

<script>
export default {
 // ...
 methods: {
  dispatchEvent (eventName, params) {
   if (typeof this.form !== 'object' && !this.form.$emit) {
    console.error('FormItem必须在Form组件内')
    return
   }
   this.form.$emit(eventName, params)
  }
 },
 mounted () {
  if (this.prop) {
   this.dispatchEvent('form.addField', {
    prop: this.prop,
    el: this.$el
   })
  }
 },
 beforeDestroy () {
  if (this.prop) {
   this.dispatchEvent('form.removeField', {
    prop: this.prop
   })
  }
 }
}
</script>

最后新建一个 index.js 导出编写好的组件。

import VForm from './Form.vue'
import FormItem from './FormItem.vue'

export {
 VForm,
 FormItem
}

3. 使用方式

表单的校验函数是在 Form 组件中。通过$ref 可以访问到 Form 组件,调用 validate 函数,从而获取到相应的校验信息。

使用方法如下:

<template>
 <v-form :model="formData" :rules="rules" ref="form">
  <form-item label="手机号" prop="tel">
   <input type="tel" maxlength="11" v-model.trim="formData.tel"/>
  </form-item>
  <button @click="handleSubmit">保存</button>
 </v-form>
</template>
<script>
 import { VForm, FormItem } from './common/Form'
 export default {
  data () {
   return {
    formData: {
     tel: ''
    },
    rules: {
     tel: [
      {required: true, message: '您的手机号码未输入'},
      {pattern: /^1[34578]\d{9}$/, message: '您的手机号码输入错误'}
     ]
    }
   }
  },
  methods: {
   handleSubmit () {
    this.$refs.form.validate(errs => {
     console.log(errs)
    })
   }
  },
  components: {
   VForm,
   FormItem
  }
 }
</script>

完整的代码点击这里。

4. 总结

本文简单介绍了一下 async-validator 的用法,并实现了一个有校验功能的 Form 组件。这里的实现的 Form 表单存在着很多的不足:(1) 仅仅是在提交表单时才去校验。(2) FormItem 组件也应该根据校验的结果调整 ui,给出相应的提示。所以,Form 组件更适合在交互较少的移动端使用。

大家可以根据这个实现思路,根据应用场景,编写定制化更高的 Form 组件。

参考资料

async-validator
Element ui 的 Form组件
Element ui 的 Form 源码

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

Javascript 相关文章推荐
javascript 面向对象全新理练之数据的封装
Dec 03 Javascript
jQuery EasyUI API 中文文档 - ComboGrid 组合表格
Oct 13 Javascript
Javascript面向对象扩展库代码分享
Mar 27 Javascript
XMLHttpRequest处理xml格式的返回数据(示例代码)
Nov 21 Javascript
jQuery中ajax的load()方法用法实例
Dec 26 Javascript
Node.js巧妙实现Web应用代码热更新
Oct 22 Javascript
AngularJS基础 ng-srcset 指令简单示例
Aug 03 Javascript
Node.js中防止错误导致的进程阻塞的方法
Aug 11 Javascript
Node.js中使用jQuery的做法
Aug 17 Javascript
详解javascript表单的Ajax提交插件的使用
Dec 29 Javascript
微信小程序如何获知用户运行小程序的场景教程
May 17 Javascript
JavaScript字符串检索字符的方法
Jun 23 Javascript
基于casperjs和resemble.js实现一个像素对比服务详解
Jan 10 #Javascript
JavaScript实现快速排序的方法分析
Jan 10 #Javascript
jQuery第一次运行页面默认触发点击事件的实例
Jan 10 #jQuery
js推箱子小游戏步骤代码解析
Jan 10 #Javascript
vue select二级联动第二级默认选中第一个option值的实例
Jan 10 #Javascript
AngularJS使用ui-route实现多层嵌套路由的示例
Jan 10 #Javascript
Vue+jquery实现表格指定列的文字收缩的示例代码
Jan 09 #jQuery
You might like
关于Iframe如何跨域访问Cookie和Session的解决方法
2013/04/15 PHP
如何使用PHP计算上一个月的今天
2013/05/23 PHP
php实现zip压缩文件解压缩代码分享(简单易懂)
2014/05/10 PHP
destoon实现商铺管理主页设置增加新菜单的方法
2014/06/26 PHP
PHP实现合并discuz用户
2015/08/05 PHP
php微信开发之带参数二维码的使用
2016/08/03 PHP
Phpstorm+Xdebug断点调试PHP的方法
2018/05/14 PHP
用js实现控制内容的向上向下滚动效果
2007/06/26 Javascript
jQuery基础知识filter()和find()实例说明
2010/07/06 Javascript
javascript中的缓动效果实现程序
2012/12/29 Javascript
JS判断不同分辨率调用不同的CSS样式文件实现思路及测试代码
2013/01/23 Javascript
jQuery easyui datagrid动态查询数据实例讲解
2013/02/26 Javascript
纯js实现瀑布流展现照片(自动适应窗口大小)
2013/04/08 Javascript
基于jquery插件制作左右按钮与标题文字图片切换效果
2013/11/07 Javascript
原生javascript实现隔行换色
2015/01/04 Javascript
javascript中createElement的两种创建方式
2015/05/14 Javascript
JS验证IP,子网掩码,网关和MAC的方法
2015/07/02 Javascript
JS实现的左侧竖向滑动菜单效果代码
2015/10/19 Javascript
JS动态增删表格行的方法
2016/03/03 Javascript
js实现数组冒泡排序、快速排序原理
2016/03/08 Javascript
jQuery实现扑克正反面翻牌效果
2017/03/10 Javascript
React Native如何消除启动时白屏的方法
2017/08/08 Javascript
javascript自定义事件功能与用法实例分析
2017/11/08 Javascript
Vue不能检测到Object/Array更新的情况的解决
2018/06/26 Javascript
js实现图片推拉门效果代码实例
2019/05/18 Javascript
[36:20]KG vs SECRET 2019国际邀请赛小组赛 BO2 第二场 8.16
2019/08/19 DOTA
JAVA SWT事件四种写法实例解析
2020/06/05 Python
css3实现元素环绕中心点布局的方法示例
2019/01/15 HTML / CSS
使用phonegap查找联系人的实现方法
2017/03/31 HTML / CSS
html如何对span设置宽度
2019/10/30 HTML / CSS
某个公司的Java笔面试题
2016/03/11 面试题
Sony C++笔试题
2013/03/10 面试题
优秀的2014年两会精神解读
2014/03/17 职场文书
大学生社会服务心得体会
2016/01/22 职场文书
[有人@你]你有一封绿色倡议书,请查收!
2019/07/18 职场文书
css3 利用transform-origin 实现圆点分布在大圆上布局及旋转特效
2021/04/29 HTML / CSS