背景

在应用ivew.design的时候,在源码中发现form表单的验证是应用Async Validator,而后就去看一下源码,理解原理并做一下整顿。

const validator = new AsyncValidator(descriptor);let model = {};model[this.prop] = this.fieldValue;validator.validate(model, { firstFields: true }, errors => {    this.validateState = !errors ? 'success' : 'error';    this.validateMessage = errors ? errors[0].message : '';    callback(this.validateMessage);    this.FormInstance && this.FormInstance.$emit('on-validate', this.prop, !errors, this.validateMessage || null);});

这个是源码的版本


先理解咋用

  • gitHub源码地址

    star还是蛮多的,很多库都用了这个,只能说666~

  • 装置

    npm i async-validator
  • 用法

    更多的细节就从源码外面去阐明了,说多了也记不得~


总体解析

  1. 大抵设计如下
  2. 最初导出的Schema构造函数

    由下面能够看出,这个js库最初导出Schema构造函数,并且分成几个大块,原型链下面的办法,register,warning,messages,validators

warning办法

在实例化 Schema 之前设置 warning 办法。该办法实际上是util.js文件外面的一个工具办法

如果放弃所有的管制太正告,你能够本人初始化Schema.warning = () => {}
//默认是一个空函数,这样if条件没有通过时将不会在控制台打印正告export let warning = () => {};// 在生产环境或者node runtime时,不打印正告信息if (  typeof process !== 'undefined' &&  process.env &&  process.env.NODE_ENV !== 'production' &&  typeof window !== 'undefined' &&  typeof document !== 'undefined') {  warning = (type, errors) => {    if (typeof console !== 'undefined' && console.warn) {      if (errors.every(e => typeof e === 'string')) {        //实际上就是一个console.warn        console.warn(type, errors);      }    }  };}

messages办法

Schema 中的message办法实际上是message.js文件中导出的实例,是依据不同类型的失败校验的提醒用的音讯模板.

//index.js//构造函数 function Schema(descriptor) {  //descriptor申明了校验规定  this.rules = null;  //应用公有属性_messages保留defaultMessages  this._messages = defaultMessages;  this.define(descriptor);}Schema.messages = defaultMessages;//message.jsexport function newMessages() {  return {    default: 'Validation error on field %s',    required: '%s is required',    enum: '%s must be one of %s',    whitespace: '%s cannot be empty',    date: {      format: '%s date %s is invalid for format %s',      parse: '%s date could not be parsed, %s is invalid ',      invalid: '%s date %s is invalid',    },    types: {      string: '%s is not a %s',      method: '%s is not a %s (function)',      array: '%s is not an %s',      object: '%s is not an %s',      number: '%s is not a %s',      date: '%s is not a %s',      boolean: '%s is not a %s',      integer: '%s is not an %s',      float: '%s is not a %s',      regexp: '%s is not a valid %s',      email: '%s is not a valid %s',      url: '%s is not a valid %s',      hex: '%s is not a valid %s',    },    string: {      len: '%s must be exactly %s characters',      min: '%s must be at least %s characters',      max: '%s cannot be longer than %s characters',      range: '%s must be between %s and %s characters',    },    number: {      len: '%s must equal %s',      min: '%s cannot be less than %s',      max: '%s cannot be greater than %s',      range: '%s must be between %s and %s',    },    array: {      len: '%s must be exactly %s in length',      min: '%s cannot be less than %s in length',      max: '%s cannot be greater than %s in length',      range: '%s must be between %s and %s in length',    },    pattern: {      mismatch: '%s value %s does not match pattern %s',    },    clone() {      // 深拷贝      const cloned = JSON.parse(JSON.stringify(this));      cloned.clone = this.clone;      return cloned;    },  };}export const messages = newMessages();

当然有必要的话,这个messages的模板本人也是能够革新的。

//index.js//Schema.prototype的原型办法中有message的办法messages(messages) {  if (messages) {      //将 _messages和参数 深度合并合并    this._messages = deepMerge(newMessages(), messages);  }  return this._messages;}//util.js//deepMerge是util.js中的一个一般合并对象的办法,先是遍历一遍对象,而后对下一层的对象应用构造,实际上只有2层的合并export function deepMerge(target, source) {  if (source) {    for (const s in source) {      if (source.hasOwnProperty(s)) {        const value = source[s];        if (typeof value === 'object' && typeof target[s] === 'object') {          target[s] = {            ...target[s],            ...value,          };        } else {          target[s] = value;        }      }    }  }  return target;}
//gitHub给到的例子import Schema from 'async-validator';const cn = {  required: '%s 必填',};const descriptor = { name: { type: 'string', required: true } };const validator = new Schema(descriptor);// 将cn与defaultMessages深层合并validator.messages(cn);...

validators

  1. 为用户提供的各种数据类型的验证办法
import validators from './validator/index';Schema.validators = validators;
  1. 以对string类型的判断为例

    • rule: 在源descriptor中,与要校验的字段名称绝对应的校验规定。始终为它调配一个field属性,其中蕴含要验证的字段的名称。
//这个样子{    [field: string]: RuleItem | RuleItem[]}//例子{name:{type: "string", required: true, message: "Name is required"}}
  • value: 源对象属性中要校验的值。
  • callback: 校验实现后须要调用的callback。传递一个Error实例数组以判断校验失败。如果校验是同步的,则能够间接返回false、Error或Error Array
callback(errors)
  • source: 传给validate 办法的源对象
  • options: 额定选项
//options的外部属性export interface ValidateOption { // 是否勾销对于有效值的外部正告 suppressWarning?: boolean; // 当第一个验证规定生成谬误时,进行解决 first?: boolean; //当指定字段的第一个验证规定生成谬误时,进行解决字段,“true”示意所有字段。 firstFields?: boolean | string[];}
  • options.messages: 蕴含校验 error message 的对象,将与 defaultMessages 进行深度合并
//所有的验证的办法都是以rule, value, callback, source, options为参数function string(rule, value, callback, source, options) {  // 须要callback进来的谬误列表  const errors = [];  //首先验证required为false或者还未填写状态就间接返回  const validate =    rule.required || (!rule.required && source.hasOwnProperty(rule.field));  if (validate) {    //isEmptyValue判断是否为空值,并且将空数组也判断为true    if (isEmptyValue(value, 'string') && !rule.required) {      return callback();    }    //应用rules办法判断是否必填    rules.required(rule, value, source, errors, options, 'string');    if (!isEmptyValue(value, 'string')) {      // 判断type,range范畴(这里有波及了len,min,max判断),以及提供正则表达式的判断      rules.type(rule, value, source, errors, options);      rules.range(rule, value, source, errors, options);      rules.pattern(rule, value, source, errors, options);      if (rule.whitespace === true) {          //为仅由空格组成的字符串增加额定的校验        rules.whitespace(rule, value, source, errors, options);      }    }  }  callback(errors);}export default string;

register

除了上述提供的validators的办法,这个register用于自定义判断

Schema.register = function register(type, validator) {  //必须是函数  if (typeof validator !== 'function') {    throw new Error(      'Cannot register a validator by type, validator is not a function',    );  }  validators[type] = validator;};

为validators验证的办法提供的rule

/** *  所有的办法的传值如下 *  @param rule 校验的规定 *  @param value source对象中该字段的值 *  @param source 要校验的source对象 *  @param errors 本次校验将要去增加的errors数组 *  @param options 校验选项 *  @param options.messages 校验的messages */export default {  required, //属性为必填  whitespace, //校验空白字符  type, //判断type属性  range, //通过传入的number类型的len,min,max 进行判断  enum: enumRule,//校验值是否存在在枚举值列表中  pattern,//校验是否合乎校验正则表达式};

type有如下类型

const custom = [    'integer',    'float',    'array',    'regexp',    'object',    'method',    'email',    'number',    'date',    'url',    'hex',  ];
// 枚举验证 官网案例const descriptor = {  role: { type: 'enum', enum: ['admin', 'user', 'guest'] },};

原型链下面的办法

  1. messages
//上文有交代,用于新增本人的谬误音讯提醒模板 messages(messages) {  if (messages) {    this._messages = deepMerge(newMessages(), messages);  }  return this._messages;},
  1. define

    注册校验规定rules

  1. getType

    通过此办法取得rule的type,并会判断此type是否是曾经定义好的validators中的type

  2. getValidationMethod

通过此办法取得rule的validator

  getValidationMethod(rule) {    //定义好了validator间接返回    if (typeof rule.validator === 'function') {      return rule.validator;    }    //收集单个rule外面定义的所有key    const keys = Object.keys(rule);    const messageIndex = keys.indexOf('message');    if (messageIndex !== -1) {      //删除message属性      keys.splice(messageIndex, 1);    }    //除了message,就只有一个key且是required,返回validators提供的required办法    if (keys.length === 1 && keys[0] === 'required') {      return validators.required;    }    //否则,最初一种状况,依据rule定义的type判断    // 如果type是非法未定义的type则间接返回false    return validators[this.getType(rule)] || false;  },
  1. validate
外围办法  1. 参数:   source_ 须要校验的对象   o  即options 形容校验的解决选项的对象(上文说的,suppressWarning,firstFields,first)   oc 即callback 校验实现后的回调函数  2. 返回值是一个Promise对象:    then 校验通过    catch({errors,fields}) 校验失败    errors是一个所有error数组,fiels是一个相似{field1:[error,error...],field2:[error,error...]}的对象

不得不说asyncMap又是怎么内容
首先它。若options.first为否值,;再依据options.firstFields是否为真值,别离执行asyncSerialArray、asyncParallelArray函数。

export function asyncMap(objArr, option, func, callback) { //判断first  if (option.first) {      //判断options.first是否为真值,若为真值,调用asyncSerialArray解决series数组    //当某一规定校验失败时,即终止校验,执行callback回调    const pending = new Promise((resolve, reject) => {      const next = errors => {        callback(errors);        return errors.length          ? reject(new AsyncValidationError(errors, convertFieldsError(errors)))          : resolve();      };      const flattenArr = flattenObjArr(objArr);      asyncSerialArray(flattenArr, func, next);    });    pending.catch(e => e);    return pending;  }    let firstFields = option.firstFields || [];  if (firstFields === true) {    firstFields = Object.keys(objArr);  }  const objArrKeys = Object.keys(objArr);  const objArrLength = objArrKeys.length;  let total = 0;  const results = [];  const pending = new Promise((resolve, reject) => {    //构建next函数包装callback,目标是将所有校验器的失败文案合并再传入callback    const next = errors => {      results.push.apply(results, errors);      total++;      if (total === objArrLength) {        callback(results);        return results.length          ? reject(              new AsyncValidationError(results, convertFieldsError(results)),            )          : resolve();      }    };    if (!objArrKeys.length) {      callback(results);      resolve();    }    objArrKeys.forEach(key => {      const arr = objArr[key];      //判断firstFields值,别离执行asyncSerialArray、asyncParallelArray函数      //asyncParallelArray函数用于实现平行校验,在某个异步校验器执行过程中,平行调用下一个校验器;      //asyncSerialArray用于实现有序校验,在异步校验器执行实现后,再启用下一个校验器。      if (firstFields.indexOf(key) !== -1) {        asyncSerialArray(arr, func, next);      } else {        asyncParallelArray(arr, func, next);      }    });  });  pending.catch(e => e);  return pending;}

最初

就这样了,有新的理解,前面再编辑,批改

喜爱,帮忙点赞????