async-validator实现原理源码解析

 更新时间:2023年01月11日 11:26:23   作者:会说话的番茄  
这篇文章主要为大家介绍了async-validator实现原理源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
(福利推荐:【腾讯云】服务器最新限时优惠活动,云服务器1核2G仅99元/年、2核4G仅768元/3年,立即抢购>>>:9i0i.cn/qcloud

(福利推荐:你还在原价购买阿里云服务器?现在阿里云0.8折限时抢购活动来啦!4核8G企业云服务器仅2998元/3年,立即抢购>>>:9i0i.cn/aliyun

async-validator 介绍

async-validator是异步的验证数据是否合法有效的工具, 内置了不同数据类型的常见验证规则。

在需要对数据进行验证的场景中,都可以考虑使用async-validator。 很多流行的组件库的表单验证都是基于async-validator的数据验证实现,如elementui、ant-design-vue、iview等

async-validator 基本使用

import AsyncValidator from 'async-validator';
// 1.声明规则 descriptor
const descriptor = {
  name: [
    {
      type: 'string',
      required: true,
      message: 'name 字段不能为空!'
    },
    // 通过调用callback, 传递验证是否通过的结果
    {
      validator: (rule, value, callback) => {
        if (value === 'muji1') {
          return callback('name 不能等于 muji1');
        }
        return callback();
      }
    },
    // 通过返回Error实例, 表示验证不通过
    {
      validator: (rule, value) => {
        if (value === 'muji2') {
          return new Error('name 不能等于 muji2');
        }
        return true;
      }
    },
    // 通过返回Promise实例, 传递验证是否通过的结果
    {
      validator: (rule, value) => {
        return new Promise((resole, reject) => {
          if (value === 'muji3') {
            return reject('name 不能等于 muji3');
          }
          return resole();
        })
      }
    }
  ],
  age: {
    type: 'number',
    // 自定义验证规则. age字段不能小于18, 小于则提示 ‘年纪太小'
    validator: (rule, value, callback) => {
        if (value < 18) {
          // 通过执行callback传递数据验证的结果
          callback('年纪太小');
        } else {
          callback();
        }
    },
  },
};
// 2.创建async-validator实例
const validator = new AsyncValidator(descriptor);
// 3.数据源
const data = { name: 'muji', age: 16 }
// 4.执行验证
validator.validate(data, function(errors, fields) {
    if (!errors) {
        // 验证成功
        console.log('验证通过');
    } else {
        // 验证失败
        console.log('验证不通过', error);
    }
})

async-validator 源码分析

从上面的基本使用中可以看到, 使用async-validator的过程主要分为:

1.创建async-validator实例

2.执行实例的validate方法时,传入数据源进行验证

async-validator 源码-构造函数

我们先分析第一步创建async-validator实例时,async-validator的构造函数做了什么事情。

constructor(descriptor: Rules) {
    this.define(descriptor);
}
define(rules: Rules) {
    // 规则配置不能为空
    if (!rules) {
      throw new Error('Cannot configure a schema with no rules');
    }
    // 规则配置必须是对象
    if (typeof rules !== 'object' || Array.isArray(rules)) {
      throw new Error('Rules must be an object');
    }
    this.rules = {};
    // 统一字段的规则配置方式为数组形式。因为给字段配置验证规则时, 可存在对象、数组的配置方式(如下). 
    /** 为name配置单条规则
    * const descriptor = {
          name: {
            type: 'string',
            required: true
          }
      }
      也可以数组形式为name配置多条规则
      const descriptor = {
          name: [
              {
                type: 'string',
                required: true
              },
              {
                type: 'string',
                validator: (rule, value) => value === 'muji'
              }
          ]
      }
    */
    Object.keys(rules).forEach(name => {
      const item: Rule = rules[name];
      this.rules[name] = Array.isArray(item) ? item : [item];
    });
}

构造函数中只是执行了define方法。

而define方法内做了以下几步:

1.验证传入的验证规则是否合法。

2.统一字段的规则配置方式为数组形式

async-validator 源码-validate方法

/** validate方法可接受三个参数
 * source: 需要验证的数据源.
 * options:验证参数(可选)
 * callback:验证完成回调(可选。validate会返回promise,因此可直接通过promise执行验证完成后的逻辑)
 */ 
validate(source: Values, options: any = {}, callback: any = () => {}): Promise<Values> {
    /**
     * 第一步
     * 处理传入参数。
     * 如果options为函数, 则将该函数设置为完成回调,使第二个参数可直接传递callback, 方便调用者使用。
    */
    if (typeof options === 'function') {
      callback = options;
      options = {};
    }
    // 此处省略了部分非核心逻辑代码
    // series保存最终的数据验证的规则集合。
    const series: Record<string, RuleValuePackage[]> = {};
    /**
     * 第二步
     * 遍历、处理rules中所有字段的验证规则。(rules为构造函数中处理的处理保存的数据)
    */
    const keys = options.keys || Object.keys(this.rules);
    keys.forEach(field => {
      const rules = this.rules[field];
      let value = source[field];
      rules.forEach(rule => {
        // 此处省略了部分非核心逻辑代码
        // 为rule添加validator验证器函数(每个规则都必须存在一个validator函数去处理数据的验证逻辑)
        // getValidationMethod就是获取该rule的validator验证函数。
        // 如果rule中存在自定义的validator配置,则直接返回。
        // 如果不存在,则尝试根据rule中的type数据类型获取内置的validator验证函数
        rule.validator = this.getValidationMethod(rule);
        if (!rule.validator) {
          return;
        }
        // 为rule补充字段、类型的信息
        rule.field = field;
        rule.fullField = rule.fullField || field;
        // 处理完rule后, 将该rule添加到series中
        series[field] = series[field] || [];
        series[field].push({
          rule,
          value,
          source,
          field: field,
        });
      });
    });
    /**
     * 第三步
     * 遍历series的验证规则,执行每条规则的validator验证函数进行数据的验证。
     * 然后asyncMap返回Promise. 可监听数据验证结果
    */
    return asyncMap(
      series,
      options,
      (data, next) => {
        // 每个规则的遍历回调。处理每条规则,并且执行规则中的验证函数validator (下面分析函数内具体逻辑)
      },
      errors => {
        // 完成回调。所有规则处理完成后执行的回调
      },
      source,
    );
  }

getValidationMethod 获取规则的数据验证函数源码

getValidationMethod(rule: InternalRuleItem) {
    // 存在自定义验证函数直接返回
    if (typeof rule.validator === 'function') {
      return rule.validator;
    }
    // 省略部分非核心逻辑代码
    // 根据指定的类型,获取对应的数据验证函数
    return validators[this.getType(rule)] || undefined;
}
// 根据规则配置项的配置,返回不同的类型
getType(rule: InternalRuleItem) {
    // 不存在type类型, pattern为正则,则使用pattern类型
    if (rule.type === undefined && rule.pattern instanceof RegExp) {
      rule.type = 'pattern';
    }
    // 省略部分非核心逻辑代码
    // 规则配置项中存在type则返回, 不存在则返回string类型
    return rule.type || 'string';
}
// async-validator中内置的数据类型。
var validators = {
  string: string,
  method: method,
  number: number,
  "boolean": _boolean,
  regexp: regexp,
  integer: integer,
  "float": floatFn,
  array: array,
  object: object,
  "enum": enumerable,
  pattern: pattern,
  date: date,
  url: type,
  hex: type,
  email: type,
  required: required,
  any: any
};

第二步中的getValidationMethod方法,为每个rule验证规则获取具体验证数据的validator验证函数。

到第三步后,遍历验证规则series集合,执行规则中的validator验证函数时,把数据传入validator函数中进行验证。

第三步完整代码

/**
  * 遍历series的验证规则,执行每条规则的validator验证函数进行数据的验证。
  * 然后asyncMap返回Promise. 可监听数据验证结果
 */
 return asyncMap(
   series,
   options,
   (data, next) => {
       const rule = data.rule;
       // 此处省略部分非核心逻辑
       let res: ValidateResult;
       /**
       * 第一步
       * 存在validator, 执行validator验证函数,不同数据类型的validator验证函数,对数据的验证逻辑不同
       */ 
       if (rule.validator) {
          /**
           * 执行validator验证器函数
           * rule: 规则
           * data.value:需要验证的值
           * cb:validator执行完成后, 通过cb函数处理验证的结果,然后执行下一个规则的验证
           * data.source:原始传入的数据源
           * options: 调用validate时传递的options
          */ 
          res = rule.validator(rule, data.value, cb, data.source, options);
        }
        /**
        * 第二步, 处理validator验证的返回结果, validator函数内部可以执行传递的cb函数传递验证的结果
        */
        if (res === true) {
          // validator返回true时,表示没有错误,直接执行cb进行下一个规则的验证。
          cb();
        } else if (res instanceof Error) {
           // validator返回Error时, 传递错误信息给cb函数, cb函数记录错误信息, 然后cb函数执行下一个规则的验证
          cb(res.message);
        } else if (res && (res as Promise<void>).then) {
          /**
           * validator验证函数中,亦可通过返回Promise传递验证的结果
           * validator返回Promise时, 注册Promise的成功、失败回调
           * 成功时:执行cb函数, 传递空, 表示不存在错误, 然后cb函数执行下一个规则
           * 失败时: 执行cb函数, 传递错误信息, 然后cb函数执行下一个规则
          */
          (res as Promise<void>).then(
            () => cb(),
            e => cb(e),
          );
        }
        /**
        * validator验证函数验证完成后,需要执行cb函数,进行验证结果的处理、记录
        * 并调用next使asyncMap执行下一个规则的验证
        */
        function cb(e: SyncErrorType | SyncErrorType[] = []) {
          let errorList = Array.isArray(e) ? e : [e];
          if (errorList.length && rule.message !== undefined) {
            errorList = [].concat(rule.message);
          }
          /**
           * complementError中会为错误信息项填充额外的信息。如出现错误的字段、出现错误的值
           */ 
          let filledErrors = errorList.map(complementError(rule, source));
          // asyncMap并不是同步循环series规则集合,而是遍历的过程中,需要等待执行next才会遍历下一个series中的规则
          // 将错误结果filledErrors传递到下一个规则的事件循环中,最后所有规则验证完成时,能够获取到所有的规则的验证结果
          next(filledErrors);
        }
   },
   // errors 即所有验证不通过的错误记录(即执行next时传递的所有filledErrors)
   errors => {
        // 所有规则处理完成后执行的回调
        let fields: ValidateFieldsError = {};
        if (!errors.length) {
          // 不存在错误, 直接执行validate时传递的完成回调
          callback(null, source);
        } else {
          // 存在错误
          // 将errors错误记录按字段分类, 如每个字段可配置多条规则, 因此每个字段可能存在多个错误记录
          // fields 数据格式如 { field1: [error1, error2], field2: [error1] }
          fields = convertFieldsError(errors);
          // 执行完成回调, 传递errors错误记录, fields错误记录分类
          (callback as (
            errors: ValidateError[],
            fields: ValidateFieldsError,
          ) => void)(errors, fields);
        }
   },
   source,
 );

以上代码主要分为以下几步:

1.遍历验证的规则集合

2.执行每条规则的validator验证函数,进行数据验证。

3.验证完成后, 执行cb函数处理、记录验证的结果,然后cb执行next处理下一条规则。

4.所有规则遍历处理完后,触发调用validate时传入的callback,并传入验证结果。

async-validator 源码-register方法

在validators中注册新的validator数据验证器。

static function register(type: string, validator) {
    if (typeof validator !== 'function') {
      throw new Error(
        'Cannot register a validator by type, validator is not a function',
      );
    }
    // 将该type的validator数据验证器函数添加到validators中
    // 后续执行数据验证时,会根据type在validators中取验证器对数据进行验证
    validators[type] = validator;
};

总结

async-validator可以分为两个部分。
1.validators验证器集合: 保存着不同type数据类型的验证函数。可以通过register对validators进行扩展。

2.validate方法: 为rule规则根据type数据类型在validators验证器集合中匹配对应的validator函数进行数据验证。大致的执行过程如下

  • 遍历外部传入的规则配置项,根据配置项中的type数据类型,获取对应数据类型的validator验证函数,得到最终的验证规则集合。
  • 遍历最终的规则集合,执行规则的validator验证函数, 处理、收集验证函数的验证结果。
  • 所有规则执行完成后,触发外部传递的callback完成函数,并且传递收集到的验证结果。

最后

async-validator中非核心流程的部分经过了省略。

以上只是我对async-validator的一点理解,希望我们能一起学习、一起进步。

最后,你可以从功能的实现、代码的组织、可读性等任何的角度思考下async-validator中做得比较好或者能够优化的地方吗?更多关于async-validator原理的资料请关注程序员之家其它相关文章!

相关文章

  • js?字符串中文下对齐问题解析

    js?字符串中文下对齐问题解析

    这篇文章主要为大家介绍了js字符串含中文下对齐问题解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-07-07
  • JS算法题解搜索插入位置方法示例

    JS算法题解搜索插入位置方法示例

    这篇文章主要为大家介绍了JS算法题解搜索插入位置方法示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-07-07
  • js中的赋值 浅拷贝和深拷贝详细

    js中的赋值 浅拷贝和深拷贝详细

    js数据类型主要分基本数据类型和引用数据类型。前者包括Number,String等,后者主要是Object,因此以下会针对不同的数据类型来分析,需要的朋友可以参考一下
    2021-09-09
  • 在web?worker中使用fetch实例详解

    在web?worker中使用fetch实例详解

    这篇文章主要为大家介绍了在web?worker中使用fetch实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • JS疑惑的数据类型及类型判断方法详解

    JS疑惑的数据类型及类型判断方法详解

    这篇文章主要为大家介绍了JS中疑惑的数据类型及类型判断方法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • 解密效果

    解密效果

    解密效果...
    2006-06-06
  • 前端JavaScript中的class类

    前端JavaScript中的class类

    这篇文章主要介绍了前端JavaScript中的class,类是用于创建对象的模板,JavaScript中的Class更多的还是语法糖,本质上绕不开原型链,下面就来看看关于JavaScript class类的详细内容吧
    2021-10-10
  • 微信小程序 支付功能实现PHP实例详解

    微信小程序 支付功能实现PHP实例详解

    这篇文章主要介绍了微信小程序 支付功能实现PHP实例详解的相关资料,需要的朋友可以参考下
    2017-05-05
  • 微信小程序 特效菜单抽屉效果实例代码

    微信小程序 特效菜单抽屉效果实例代码

    这篇文章主要介绍了微信小程序 特效菜单抽屉效果实例代码的相关资料,抽屉效果的菜单在APP应用中经常使用,这里用微信小程序来实现这一效果,需要的朋友可以参考下
    2017-01-01
  • 前端的状态管理(下)

    前端的状态管理(下)

    这篇文章主要介绍了前端的状态管理,续上篇文章内容,今天将从 Redux 入手逐渐拓展,需要的小伙伴可以参考一下哟
    2021-10-10

最新评论

?


http://www.vxiaotou.com