Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

element-UI form,form-item组件中validate的实现 #22

Open
tyz98 opened this issue Dec 3, 2020 · 0 comments
Open

element-UI form,form-item组件中validate的实现 #22

tyz98 opened this issue Dec 3, 2020 · 0 comments

Comments

@tyz98
Copy link
Contributor

tyz98 commented Dec 3, 2020

element-UI form,form-item组件中validate相关源码

核心:form-item的validate方法 (使用了async-validator)

validate(trigger, callback = noop)

trigger: string || 空

callback接受两个参数(message, invalidFields)
//作用:找出所有trigger包含指定trigger的rules,作为descriptor对this.fieldValue进行验证,验证完毕更新validateState和validateMessage,并调用指定的回调函数callback(this.validateMessage, invalidFields)

注意使用的validator.validate中传入了option为{ firstFields: true }, errors中只有一个错误

async-validator validate方法API文档

import AsyncValidator from 'async-validator';//引入async-validator

validate(trigger, callback = noop) {
  this.validateDisabled = false;//?
  const rules = this.getFilteredRule(trigger);//取出所有可被触发的rules数组
  if ((!rules || rules.length === 0) && this.required === undefined) {
    callback();
    return true;
  }
  this.validateState = 'validating';//开始验证,validateState为'validating'
  const descriptor = {};
  if (rules && rules.length > 0) {
    rules.forEach(rule => {
      delete rule.trigger;//async-validator的descriptor中没有trigger这个属性,所以删除
    });
  }
  descriptor[this.prop] = rules;//传入async-validator构造函数的descriptor
  const validator = new AsyncValidator(descriptor);//AsyncValidator
  const model = {};
  model[this.prop] = this.fieldValue;//传入async-validator的validate方法的model(待validate的对象)
  //validate方法文档见代码下方
  validator.validate(model, { firstFields: true }, (errors, invalidFields) => {//验证完毕的回调:errors is an array of all errors, fields is an object keyed by field name with an array of errors per field
    this.validateState = !errors ? 'success' : 'error';//validate完毕更新validateState为'success'或'error'
    this.validateMessage = errors ? errors[0].message : '';//validate完毕更新validateMessage为所有errors中的第一个error的message(error构造函数中传入的字符串就是这个error对象的.message)
    callback(this.validateMessage, invalidFields);//验证完毕调用this.validate方法传入的callback(错误message,错误的fields[{name: [error,,,]}])
    this.elForm && this.elForm.$emit('validate', this.prop, !errors, this.validateMessage || null);//elForm emit一个validate事件,参数为validate完成的是哪一个prop, 是否正确, 验证message(为什么这里用了inject的elForm没有用form???)
  });
},

哪里在调用validate

1. form-item的onFieldBlur和onFieldChange

onFieldBlur() {
  this.validate('blur');//调用this.validate
},
onFieldChange() {
  if (this.validateDisabled) {
    this.validateDisabled = false;
    return;
  }
  this.validate('change');//调用this.validate
},
addValidateEvents() {
  const rules = this.getRules();
  if (rules.length || this.required !== undefined) {
    this.$on('el.form.blur', this.onFieldBlur);//el.form.blur时触发this.onFieldBlur
    this.$on('el.form.change', this.onFieldChange);//el.form.blur时触发this.onFieldChange
  }
},
removeValidateEvents() {//父form的rules改变时调用,先removeValidateEvents再addValidateEvents
  this.$off();//解除'el.form.blur'和'el.form.change'事件的监听
}

2. form的validateField

validateField(props, cb)

依次调用props对应的form-item的validate方法,cb传入每次validate的callback(仍是接受两个参数(message, invalidFields))

form-item的validate方法第一个参数trigger传入'',即不论rules中的trigger为何值都触发验证

validateField(props, cb) {
  props = [].concat(props);
  const fields = this.fields.filter(field => props.indexOf(field.prop) !== -1);
  if (!fields.length) {
    console.warn('[Element Warn]please pass correct props!');
    return;
  }//filter出props对应的fields(form-item数组)
  fields.forEach(field => {//依次调用form-item的validate方法
    field.validate('', cb);
  });
},

3. form的validate

validate(callback)

callback为function时接受两个参数(valid, invalidFields) valid为boolean验证通过为true, invalidFields为array [{name,[error,]},]

callback若为空或不为function,返回一个promise,验证结束时promise状态改变

validate(callback) {
  if (!this.model) {
    console.warn('[Element Warn][Form]model is required for validate to work!');
    return;
  }
  let promise;
  // if no callback, return promise
  if (typeof callback !== 'function' && window.Promise) {
    promise = new window.Promise((resolve, reject) => {
      callback = function(valid) {
        valid ? resolve(valid) : reject(valid);
      };
    });
  }
  let valid = true;
  let count = 0;
  // 如果需要验证的fields为空,调用验证时立刻返回callback
  if (this.fields.length === 0 && callback) {
    callback(true);
  }
  let invalidFields = {};
  this.fields.forEach(field => {
    field.validate('', (message, field) => {
      if (message) {
        valid = false;
      }
      invalidFields = objectAssign({}, invalidFields, field);//把所有form-item的错误汇总到invalidFields中
      if (typeof callback === 'function' && ++count === this.fields.length) {//如果所有form-item都验证完毕且callback是function,则调用callback
      //注:本身传入的callback不是function时,在上面promise的executor中也把callback变成了function,这个callback可以改变所返回promise的状态,若valid=true则promise resolve,否则reject
        callback(valid, invalidFields);
      }
    });
  });
  if (promise) {//如果传入的callback不是function,返回promise,验证完毕时promise的状态会改变
    return promise;
  }
},

form与form-item生命周期钩子

关系到form-item在form中的添加和删除

form created

form创建,监听两个事件'el.form.addField'和'el.form.removeField',管理this.fields

created() {
  this.$on('el.form.addField', (field) => {//子form-item mounted时,把form-item实例添加进this.fields
    if (field) {
      this.fields.push(field);
    }
  });
  /* istanbul ignore next */
  this.$on('el.form.removeField', (field) => {//子form-item beforeDestroyed时,把form-item实例从this.fields中删除
    if (field.prop) {//这个判断对应form-item mounted中的if(this.prop), 只有有prop的item才添加,自然也只有有prop的item才需要删除
      this.fields.splice(this.fields.indexOf(field), 1);
    }
  });
},

form-item mounted

form-item mounted时触发'el.form.addField',并记录此form-item的初始值(resetField时需要用),且为此form-item添加'el.form.blur'和'el.form.change'监听,回调为validate方法

mounted() {
  if (this.prop) {
    this.dispatch('ElForm', 'el.form.addField', [this]);////form-item mounted时触发el.form.addField事件,此时form把本form-item实例添加到form.fields中(只有有prop属性时才传入触发事件通知form)
    let initialValue = this.fieldValue;
    if (Array.isArray(initialValue)) {
      initialValue = [].concat(initialValue);
    }
    Object.defineProperty(this, 'initialValue', {
      value: initialValue
    });//form-item mounted时还记录此form-item的初始值(resetField时需要用)
    this.addValidateEvents();//form-item mounted时添加'el.form.blur'和'el.form.change'监听,回调为validate方法(具体见form-item validate方法 ‘哪里在调用validate’)
  }
},

form-item mounted

form-item销毁时触发'el.form.removeField'

beforeDestroy() {
  this.dispatch('ElForm', 'el.form.removeField', [this]);
}

上面涉及到的关键method/computed

form (computed)

找到这个form-item的父form(form-item中可能嵌套form-item)

form() {
  let parent = this.$parent;
  let parentName = parent.$options.componentName;
  while (parentName !== 'ElForm') {
    if (parentName === 'ElFormItem') {
      this.isNested = true;
    }
    parent = parent.$parent;
    parentName = parent.$options.componentName;
  }
  return parent;
},

fieldValue (computed)

返回父form.model[this.prop]

fieldValue() {
  const model = this.form.model;//model是父form的prop
  if (!model || !this.prop) { return; }
  let path = this.prop;
  if (path.indexOf(':') !== -1) {
    path = path.replace(/:/, '.');
  }
  return getPropByPath(model, path, true).v;//getPropByPath方法(在element的utils中,用于以字符串的形式寻找相应属性)
},

getRules

返回rules数组(如果这个form-item本身传入了rules prop,则返回的是rules+本身传入的required prop;如果本身未传入rules prop,则返回的是父form中提取处的本item的rules+本身传入的required prop)

所以若form-item本身传入了rules prop,则忽略form中与本item有关的prop

getRules() {
  let formRules = this.form.rules;//this.form.rules是父form中传入的prop
  const selfRules = this.rules;//this.rules是本form-item传入的prop (rules: [Object, Array])????
  const requiredRule = this.required !== undefined ? { required: !!this.required } : [];
  const prop = getPropByPath(formRules, this.prop || '');
  formRules = formRules ? (prop.o[this.prop || ''] || prop.v) : [];//getPropByPath方法
  return [].concat(selfRules || formRules || []).concat(requiredRule);
},

getFilteredRule

返回可以触发验证的rules数组(所有rules中,trigger包含指定trigger的rule和没明确trigger的rule)

getFilteredRule(trigger) {
  const rules = this.getRules();
  return rules.filter(rule => {
    if (!rule.trigger || trigger === '') return true;//rule中没明确trigger时,任何trigger都可以触发
    if (Array.isArray(rule.trigger)) {
      return rule.trigger.indexOf(trigger) > -1;
    } else {
      return rule.trigger === trigger;
    }
  }).map(rule => objectAssign({}, rule));
},
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant