到目前为止,我们的基本功能就已经实现了,除了提交与验证规则外,所有的组件几乎与 ElementUI 中的表单一模一样了。下面我们就开始实现校验功能。
4. 设计校验规则
在上面设计的组件中,我们知道校验当前项和展示错误信息的工作是在 FormItem 组件中,但是数据的变化是在 Input 组件中,所以 FormItem 和 Input 组件是有数据传递的。当 Input 中的数据变化时,要告诉 FormItem ,让 FormItem 进行校验,并展示错误。
首先,我们修改一下 Input 组件:
methods: { handlerInput(event) { this.valueInInput = event.target.value; this.$emit("input", this.valueInInput); // 数据变了,定向通知 FormItem 校验 this.dispatch('EFormItem', 'validate', this.valueInput); }, // 查找指定 name 的组件, dispatch(componentName, eventName, params) { var parent = this.$parent || this.$root; var name = parent.$options.name; while (parent && (!name || name !== componentName)) { parent = parent.$parent; if (parent) { name = parent.$options.name; } } if (parent) { parent.$emit.apply(parent, [eventName].concat(params)); } } }
这里,我们不能用 this.$emit 直接派发事件,因为在 FormItem 组件中, Input 组件的位置只是一个插槽,无法做事件监听,所以此时我们让 FormItem 自己派发事件,并自己监听。修改 FormItem 组件,在 created 中监听该事件。
created() { this.$on('validate', this.validate); }
当 Input 组件中的数据变化时, FormItem 组件监听到 validate 事件后,执行 validate 函数。
下面,我们就要处理我们的 validate 函数了。而在 ElementUI 中,验证用到了一个底层库async-validator,我们可以通过 npm 安装这个包。
npm i async-validator
async-validator 是一个可以对数据进行异步校验的库,具体的用法可以参考上面的链接。我们通过这个库来完成我们的 validate 函数。继续看 FormItem.vue 这个文件:
<template> <div> <label v-if="label">{{ label }}</label> <div> <slot></slot> <p v-if="validateState === 'error' ">{{ validateMessage }}</p> </div> </div> </template> <script> import AsyncValidator from "async-validator"; export default { name: "EFormItem", props: { label: { type: String, default: '' }, prop: { type: String, default: '' } }, inject: ["eForm"], // 解释一 created() { this.$on("validate", this.validate); }, mounted() { // 解释二 if (this.prop) { // 解释三 this.dispatch('EForm', 'addFiled', this); } }, data() { return { validateMessage: "", validateState: "" }; }, methods: { validate() { // 解释四 return new Promise(resolve => { // 解释五 const descriptor = { // name: this.form.rules.name => // name: [ { require: true }, { ... } ] }; descriptor[this.prop] = this.eForm.rules[this.prop]; // 校验器 const validator = new AsyncValidator(descriptor); const model = {}; model[this.prop] = this.eForm.model[this.prop]; // 异步校验 validator.validate(model, errors => { if (errors) { this.validateState = "error"; this.validateMessage = errors[0].message; resolve(false); } else { this.validateState = ""; this.validateMessage = ""; resolve(true); } }); }); }, // 查找上级指定名称的组件 dispatch(componentName, eventName, params) { var parent = this.$parent || this.$root; var name = parent.$options.name; while (parent && (!name || name !== componentName)) { parent = parent.$parent; if (parent) { name = parent.$options.name; } } if (parent) { parent.$emit.apply(parent, [eventName].concat(params)); } } } }; </script> <style scoped> .error { color: red; } </style>
我们对上面的代码做一个解释。
解释一:注入 Form 组件提供的数据 - Form 组件的实例,下面就可以使用 this.eForm.xxx 来使用 Form 中的数据了。
解释二:因为我们需要在 Form 组件中校验所有的 FormItem ,所以当 FormItem 挂载完成后,需要派发一个事件告诉 Form :你可以校验我了。
解释三:当 FormItem 中有 prop 属性的时候才校验,没有的时候不校验。比如提交按钮就不需要校验。
<e-form-item> <input type="submit" @click="submitForm()" value="提交"> </e-form-item>
**解释四:**返回一个 promise 对象,批量处理所有异步校验的结果。
解释五: descriptor 对象是 async-validator 的用法,采用键值对的形式,用来检查当前项。比如:
// 检查当前项 // async-validator 给出的例子 name: { type: "string", required: true, validator: (rule, value) => value === 'muji', }