使用 async-validator 编写 Form 组件__前端__Element__设计__Vue.js
发布于 3 年前 作者 banyungong 1434 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

前端开发中,表单的校验一个很常见的功能,一些 ui 库例如 ant.designElement 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 组件结合起来呢。

实现思路

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

Form与FormItem组件的实现思路

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 组件。

参考资料

回到顶部