问题描述:在使用日期组件DatePicker时,需要自定义一个日期校验规则,为了后续的重复使用,需要将改校验方法抽成一个公用的校验方法,在抽象时需要几个自定义的参数用于辅助处理,那么这些参数该如何传递到自定义的校验方法中呢?

需求如下:

// 伪代码
function 自定义校验(test1, test2, callback) {
    if (value === 'test1') {
        callback()
        } else (value === 'test2') {
            callback()
        } else {
            callback()
        }
}

element官方的自定义校验规则实现方式如下,先写好自定义校验函数,然后直接将该函数赋值给相应的prop即可,官方实例

// 不相关的内容被删减掉了
<el-form  :rules="rules" ref="ruleForm" >
  <el-form-item label="年龄" prop="age">
    <el-input></el-input>
  </el-form-item>
</el-form>

<script>
  export default {
    data() {
      // 定义校验函数
      var checkAge = (rule, value, callback) => {
        // 以下是校验规则
        if (!value) {
          return callback(new Error('年龄不能为空'));
        }
        setTimeout(() => {
          if (!Number.isInteger(value)) {
            callback(new Error('请输入数字值'));
          } else {
            if (value < 18) {
              callback(new Error('必须年满18岁'));
            } else {
              callback();
            }
          }
        }, 1000);
      };
        
      return {
        rules: {
          age: [
             // 调用自定义校验函数
            { validator: checkAge, trigger: 'blur' }
          ]
        }
      };
    },
        
    methods: {
     ...
    }
  }
</script>

通过上述可以看到表单在掉用自定义校验方法时直接将校验函数赋值给validator,以一个校验规则的形式成为age数组中的一个对象,为了方便理解,这里成为校验规则对象{ validator: checkAge, trigger: ‘blur’ }(一个prop可以有多个校验规则对象,所以age是一个数组)

且上方校验函数的入参有三个(rule, value, callback)

新的思考:①这三个参数分别是什么;②校验函数的入参必须这么写吗?

解答①rule是存放接收参数的对象;value是待校验的值;callback是回调函数(校验完后,要执行的操作,如抛错)②必须这样,为什么后面有解释

问题重述:如果根据我上面的需求,期望根据传入的test1和test2进行校验,且test1和test2的值是可动态调整,那么该如何实现

// 伪代码
function checkTime(rule, value, callback) {
    if (value === 'test1') {
        callback()
        } else (value === 'test2') {
            callback()
        } else {
            callback()
        }
}

研究发现校验函数要接收的参数直接以此作为一个属性放到校验规则对象中即可。

// 伪代码
rules: {
 startDate: [
	{ validator: checkTime, test1: 'test1', test2: 'test2' }
	],
}

打印rule会有如下结果

// 伪代码
function checkTime(rule, value, callback) {
    console.log(rule, value)
    ...
}

elementplus自定义弹出层背景 element ui自定义组件_elementplus自定义弹出层背景

上述说明参数test1和test2已经传入到rule中,剩下的就是根据自己的校验逻辑编写即可。所以element的自定义校验规则如何传参已经解决。

新的思考:我们理解的正常的传参应该如下所示:

正常的传参是

// 伪代码
function  checkTime(test1, test2) {}

// 调用
const validator = checkTime('test1', 'test2')

而上述③

{ validator: checkTime, test1: 'test1', test2: 'test2' }

是如何接收参数的,其中打印的结果中④field,fullField,type等字段并未传递又是怎样出来的?

对于③④我们猜测应该在未知的包中存在如下逻辑代码

// 伪代码
let rule = {}
rule.test1 = rules[startDate][0].test1
rule.test2 = rules[startDate][0].test2
rule.field = rules.prop 即 'startDate'
rule.fullField = rules.prop 即 'startDate'
rule.type = '???'
rule.validator = rules[startDate][0].validator

// 并且存在如下调用
const r = rule.validator(rule, value, callback)

最后通过查看element UI源码发现是由element引用的async-validator包中的调用方式所决定的

在async-validator代码中有下面这样一些处理

// 局部源码
rule.validator = _this.getValidationMethod(rule);
        rule.field = z;
        rule.fullField = rule.fullField || z;
        rule.type = _this.getType(rule);
        
// 局部源码

var res = rule.validator(rule, data.value, cb, data.source, options);

if (res && res.then) {
        res.then(function () {
          return cb();
        }, function (e) {
          return cb(e);
        });
      }

通过上述源码证实了我们的猜想,也验证了②必须以这样的格式(async-validator包中处理格式)实现。

延申:

如果想自己研究源码可以按照如下的方式进行:

找到node_modules包中element-ui包打开packages/form/src可以看到form-item.vue、form.vue、label-wrap.vue三个vue文件,其中label-wrap.vue主要是计算表单项的宽度,form.vue是整体表单逻辑,form-item.vue是表单里具体项逻辑处理,form-item.vue文件中会以此触发一下函数

// step1 300行
mounted() {
    this.addValidateEvents();
}
// step2 288行
methods: {
    addValidateEvents() {
        const rules = this.getRules();

        if (rules.length || this.required !== undefined) {
          this.$on('el.form.blur', this.onFieldBlur);
          // step3 监听表单变化
          this.$on('el.form.change', this.onFieldChange);
        }
      },
    // step4 278行
    onFieldChange() {
        if (this.validateDisabled) {
          this.validateDisabled = false;
          return;
        }

        this.validate('change');
      },
    // step5 189行
    validate(trigger, callback = noop) {
        this.validateDisabled = false;
        const rules = this.getFilteredRule(trigger);
        if ((!rules || rules.length === 0) && this.required === undefined) {
          callback();
          return true;
        }

        this.validateState = 'validating';

        const descriptor = {};
        if (rules && rules.length > 0) {
          rules.forEach(rule => {
            delete rule.trigger;
          });
        }
        descriptor[this.prop] = rules;
		// step6 207行 此处调用async-validator包
        const validator = new AsyncValidator(descriptor);
        const model = {};

        model[this.prop] = this.fieldValue;
		// step7 到async-validator继续查找
        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);
        });
      }
}

step 8 根据上述导航打开node_modules/async-validator/es/index.js

该文件中定义了一个Schema类,并在该类中定义了validate方法43行

// step9 43hang 
validate: function validate(source_) {}

在该方法中有以下内容

// step10 
validate: function validate(source_) {
    ...
    rule.validator = _this.getValidationMethod(rule); // 124
    rule.field = z; //125
    rule.fullField = rule.fullField || z; //126
    rule.type = _this.getType(rule); 127
    ...
    var res = rule.validator(rule, data.value, cb, data.source, options);// 216
      if (res && res.then) {
        res.then(function () {
          return cb();
        }, function (e) {
          return cb(e);
        });
      }
}

延申二可以在控制台Sources菜单下通过搜索上述所设计到的函数,定位到相应的位置进行debugger,然后自己动手调试调试。