使用 async-validator 編寫 Form 組件

前端開發中,表單的校驗一個很常見的功能,一些 ui 庫例如 ant.designElement ui 都實現了有校驗功能的 Form 組件。async-validator 是一個能夠對數據進行異步校驗的庫,ant.design 與 Element ui 的 Form 組件都使用了 async-validator。本文就簡單介紹一下 async-validator 的基本用法以及使用該庫實現一個簡單的有校驗功能的 Form 組件。html

1. async-validator 的基本用法

async-validator 的功能是校驗數據是否合法,而且根據校驗規則給出提示信息。前端

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

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 的回調函數中,就能夠獲得相應的錯誤信息。git

當 async-validator 中常見的校驗規則沒法知足需求時,咱們能夠編寫自定義的校驗函數來校驗數據。一個簡單的校驗函數以下。github

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 都要調用。npm

2. 編寫 Form 組件與 FormItem 組件

如今知道了 async-validator 的使用方法,如何將這個庫跟要編寫的 Form 組件結合起來呢。api

實現思路

用一張圖描述一下實現思路。 數組

Form與FormItem組件的實現思路

Form 組件

Form 組件應該是一個容器,裏面包含不定數量的 FormItem 或者其餘元素。可使用 Vue 內置的 slot 組件來表明 Form 裏面的內容。異步

Form 組件還須要知道包含了多少個須要校驗的 FormItem 組件。通常狀況下,父子組件的通訊 是經過在子組件上綁定事件實現的,可是這裏使用 slot,沒法監聽到子組件的事件。這裏能夠在 Form 組件上經過 $on 監聽事件,FormItem 掛載或者銷燬前觸發 Form 組件的自定義事件便可。async

按照這個思路,咱們先編寫 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 組件。

參考資料

相關文章
相關標籤/搜索