关于javascript:来来来~Async-Validator源码看一下

背景

在应用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.js
export 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;
}

最初

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

喜爱,帮忙点赞????

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理