深入Element Form表单验证,【附】实战小技巧

863 阅读4分钟

你应该知道的 ElementUI 使用小技巧

缘由

在之前的《vue elementUI 组件表单动态验证失效的问题与解决办法》中,讲到直接修改prop属性,未触发form-item的重新渲染,所以虽然有校验*的标志,实际上并不会校验,所以去看了看源码。

源码分析

  1. form组件的created钩子函数中添加了el.form.addFieldel.form.removeField事件监听,往fields中添加或删除field,校验的时候会遍历 fields 数组,而field就是form-item的实例。接着查看form-item组件的源码,可以看到在组件挂载后mounted,如果prop属性有值就会触发el.form.addField事件,在组件销毁前beforeDestroy触发el.form.removeField事件。由此可知如果挂载时form-item组件prop属性无值,不会触发el.form.addField
// form.vue
created() {
  this.$on('el.form.addField', (field) => {
    if (field) {
      this.fields.push(field);
    }
  });
  /* istanbul ignore next */
  this.$on('el.form.removeField', (field) => {
    if (field.prop) {
      this.fields.splice(this.fields.indexOf(field), 1);
    }
  });
}

// form-item.vue
mounted() {
  // 重点挂载前有prop属性,才会触发'el.form.addField'
  if (this.prop) {
    this.dispatch('ElForm', 'el.form.addField', [this]);
    // ...
    // 增加触发器的事件
    this.addValidateEvents();
  }
},
beforeDestroy() {
  this.dispatch('ElForm', 'el.form.removeField', [this]);
}
  1. form表单校验是调用表单实例的validate方法,去掉边界、合法性判断一类的代码,其核心源码如下,从代码注释中可以知道,form表单的校验方法核心是调用form-item组件实例的validate方法。
// form.vue
methods: {
  validate(callback) {
    // ...
    this.fields.forEach(field => {
      // 调用form-item实例的validate方法
      field.validate('', (message, field) => {
        if (message) {
          valid = false;
        }
        invalidFields = objectAssign({}, invalidFields, field);
        if (typeof callback === 'function' && ++count === this.fields.length) {
          callback(valid, invalidFields);
        }
      });
    });
  }
},

watch: {
  // 如果rules有变化,强制更新form-item的校验事件,并触发一次form实例validate方法
  rules() {
    // remove then add event listeners on form-item after form rules change
    this.fields.forEach(field => {
      field.removeValidateEvents();
      field.addValidateEvents();
    });

    if (this.validateOnRuleChange) {
      this.validate(() => {});
    }
  }
}
  1. 最后看form-itemvalidate方法,省略一些逻辑判断和优化代码,其核心是获取form组件的对应属性的rulesform-item自身的rules,生成AsyncValidator校验器的校验规则描述,并进行数据校验。
// form-item.vue
validate(trigger, callback = noop) {
  this.validateDisabled = false;
  // 获取form组件的对应属性的rules及form-item自身的rules,并根据触发器trigger过滤
  const rules = this.getFilteredRule(trigger);
  // 判断校验规则,假值或者rules数组为空,则跳过。值得注意的是空对象并不是假值,所以不会跳过
  if ((!rules || rules.length === 0) && this.required === undefined) {
    callback();
    return true;
  }

  this.validateState = 'validating';

  // 设置AsyncValidator的校验规则描述
  const descriptor = {};
  if (rules && rules.length > 0) {
    rules.forEach(rule => {
      delete rule.trigger;
    });
  }
  descriptor[this.prop] = rules;

  const validator = new AsyncValidator(descriptor);
  const model = {};

  model[this.prop] = this.fieldValue;

  validator.validate(model, { firstFields: true }, (errors, invalidFields) => {
    this.validateState = !errors ? 'success' : 'error';
    this.validateMessage = errors ? errors[0].message : '';

    callback(this.validateMessage, invalidFields);
    this.elForm && this.elForm.$emit('validate', this.prop, !errors, this.validateMessage || null);
  });
}

从上面的可以看到一下三点:

  1. prop初始值为空或者字符串'',则form表单的fileds不会持有该form-item实例,故即使后面修改prop属性,也不会去校验该表单项,导致校验失效。
  2. 规则中的触发器trigger在只会挂载后 mounted 钩子函数中加入到校验规则中。
  3. el-form-item 组件在 el-form 组件的插槽内,无论处于哪个组件中,都会加入到校验队列中。

动态校验

  1. form-item保留prop属性,根据条件动态修改form组件的rules对象。比如下面这样:
watch: {
  'formData.age': {
    immediate: true,
    handler (newVal) {
      if (newVal >= 18) {
        this.addRule('bankCardNo', [{ required: true, message: '请输入银行卡号', trigger: 'blur' }])
      } else {
        this.addRule('bankCardNo', [])
      }
    }
  }
}

data () {
  return {
    rules: {}
  }
},

methods: {
  addRule (prop, rule) {
    this.$set(this.rules, prop, rule)
  }
}
  1. form-item保留prop属性,根据条件动态修改form-item组件的rules属性。比如下面两种方式,因为rules属性接受数据和对象。
<el-form-item
  label="银行卡号"
  prop="bankCardNo"
  :rules="formData.age >= 18 ? [{ required: true, message: '请输入银行卡号', trigger: 'blur' }] : []"
>
  <el-input v-model="formData.bankCardNo"></el-input>
</el-form-item>

<!-- 或者 -->
<el-form-item
  label="银行卡号"
  prop="bankCardNo"
  :rules="formData.age >= 18 ? { required: true, message: '请输入银行卡号', trigger: 'blur' } : []"
>
  <el-input v-model="formData.bankCardNo"></el-input>
</el-form-item>
  1. 《vue elementUI 组件表单动态验证失效的问题与解决办法》中一样通过 v-if 触发 el-form-item 的重新挂载。(OS:当时真滴是傻,不过也算歪打正着了)

实战一:单元格静态校验

相信大伙肯定遇到过这样的需求,在一个表单内容里面嵌套一个表格,同时需要校验表格里面的某些单元格内容,如果自己实现这样的校验,一般就只能遍历表格内容进行判断,然后修改标识符给出提示。 若使用的是 elementUI 组件库,那么这件事其实就变得非常简单了,只需要在表格单元格放入 el-form-item 组件下,即可实现指定单元格的校验。

<template>
  <el-form :model="form" label-position="left" label-width="100">
    <el-form-item label="其他表单项" prop="name" :rules="rules.name">
      <el-input v-model="form.name"></el-input>
    </el-form-item>
    <el-form-item label="表格Table">
      <el-table :data="form.tableData" border stripe>
        <el-table-column label="该列单元格校验">
          <template slot-scope="{ row, $index }">
            <el-form-item
              :prop="`tableData.${$index}.input`"
              :rules="rules.input"
            >
              <el-input v-model="row.input"></el-input>
            </el-form-item>
          </template>
        </el-table-column>
      </el-table>
    </el-form-item>
  </el-form>
</template>

<script>
export default {
  name: "Demo",

  data() {
    return {
      form: {
        name: "",
        tableData: [
          { input: "" },
          { input: "" },
          { input: "" },
          { input: "" },
          { input: "" }
        ]
      },
      rules: {
        name: [
          { required: true, trigger: ["blur", "change"], message: "请选择" }
        ],
        input: [
          { required: true, trigger: ["blur", "change"], message: "请选择" }
        ]
      }
    };
  }
};
</script>

其效果如下:

实战二:单元格动态校验

根据上面的方法,结合深入了解 Element Form 表单动态验证问题,即可实现单元格动态校验。

<template>
  <div>
    <el-form ref="form" :model="form" label-position="left" label-width="100">
      <el-form-item label="其他表单项" prop="name" :rules="rules.name">
        <el-input v-model="form.name"></el-input>
      </el-form-item>
      <el-form-item label="表格Table">
        <el-table :data="form.tableData" border stripe>
          <el-table-column label="是否校验">
            <template slot-scope="{ row, $index }">
              <el-form-item
                :prop="`tableData.${$index}.radio`"
                :rules="rules.radio"
              >
                <el-radio-group v-model="row.radio">
                  <el-radio label="Y">是</el-radio>
                  <el-radio label="N">否</el-radio>
                </el-radio-group>
              </el-form-item>
            </template>
          </el-table-column>
          <el-table-column label="动态校验">
            <template slot-scope="{ row, $index }">
              <el-form-item
                :prop="`tableData.${$index}.remark`"
                :rules="row.radio === 'N' ? [] : rules.remark"
                :required="false"
              >
                <el-input v-model="row.remark"></el-input>
              </el-form-item>
            </template>
          </el-table-column>
        </el-table>
      </el-form-item>
    </el-form>
    <el-button type="primary" @click="submit">提交</el-button>
  </div>
</template>

<script>
export default {
  name: "Demo",

  data() {
    return {
      form: {
        name: "",
        tableData: [
          { radio: "", remark: "" },
          { radio: "", remark: "" },
          { radio: "", remark: "" },
          { radio: "", remark: "" },
          { radio: "", remark: "" }
        ]
      },
      rules: {
        name: [
          { required: true, trigger: ["blur", "change"], message: "请输入" }
        ],
        radio: [
          { required: true, trigger: ["blur", "change"], message: "请输入" }
        ],
        remark: [
          { required: true, trigger: ["blur", "change"], message: "请输入" }
        ]
      }
    };
  },

  methods: {
    submit() {
      this.$refs.form.validate(bool => {
        if (bool) {
          console.log("submit");
        }
      });
    }
  }
};
</script>

其效果如下:

总结

没啥好总结的,踩得坑多了,就会想着去看看源码。。。