async-validator实现原理源码解析

目录
  • async-validator 介绍
  • async-validator 基本使用
  • async-validator 源码分析
    • async-validator 源码-构造函数
    • async-validator 源码-validate方法
    • async-validator 源码-register方法
  • 总结
  • 最后

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原理的资料请关注我们其它相关文章!

(0)

相关推荐

  • vant(ZanUi)结合async-validator实现表单验证的方法

    最近在开发一个移动端商城项目,用到了有赞的 vant,因为最近大都采用 element ui在做PC端的东西,对比来说,vant的完成度还是偏低了点,很多细节都虽然都实现了接口,但是想使用得自己去想办法,没办法拿来即用.昨天用到 Uploader 图片上传 如是,提供了file回调,却没有提供上传功能,我必须给他加2个函数实现axios提交才能用,还有今天用到表单验证这块,它的 Field组件虽然给了error-message的错误提示接口,但是没有内置表单验证功能. element ui采用a

  • vue3 + async-validator实现表单验证的示例代码

    目录 vue3 表单验证 前言 搭建vue3的项目 vue3的表单验证 1.表单代码 2.添加验证 2-1. 初始化 2-2. 多个表单的验证 2-3. Promise方式验证 2-4. 正则验证 2-5. 长度控制 2-6. 多个验证条件 2-5. 自定义验证 3.优化完善 vue3 表单验证 前言 表单验证可以有效的过滤不合格的数据,减少服务器的开销,并提升用户的使用体验. 今天我们使用 vue3 来做一个表单验证的例子.先来看看完成的效果图 搭建vue3的项目 创建项目前 这里我们首先要说

  • 使用async-validator编写Form组件的方法

    前端开发中,表单的校验一个很常见的功能,一些 ui 库例如ant.design与Element ui都实现了有校验功能的 Form 组件.async-validator是一个可以对数据进行异步校验的库,ant.design 与 Element ui 的 Form 组件都使用了 async-validator.本文就简单介绍一下 async-validator 的基本用法以及使用该库实现一个简单的有校验功能的 Form 组件. 1. async-validator 的基本用法 async-vali

  • Async Validator 异步验证使用说明

    async-validator 是一个异步验证的库,需要传入要验证的数据和验证规则 官方链接 https://github.com/yiminghe/async-validator 要检验的数据,格式要求如下: { a:xxx, b:xxx } 检验规则定义格式如下: { a:[ {验证规则, message: 'xxx'}, {验证规则, message: 'xxx'} ], b:[ {验证规则, message: 'xxx'} ] } message是规则没通过时返回的错误消息 举个例子,

  • React中使用async validator进行表单验证的实例代码

    react中进行表单验证毫无疑问是繁琐的,尤其对于动态添加或删除的表单,其验证逻辑更为复杂, 目前UI框架使用material ui ,但其表单处理不太理想,而后研究了一下另一个UI 框架 ant design, 其对表单的处理大大方便了逻辑的编写, 它使用async-validator处理验证逻辑 目前更换框架毫无疑问是不现实的,于是就想直接引入async-validator,下面描述一个简单的使用,具体信息可以去github上查看 validate.js import Schema from

  • Spring的Model 和 Map的原理源码解析

    Model 和 Map 为什么在Model和Map中放值传入后会出现在request的上面. 9.1.源码解析 准备测试代码 @GetMapping("/goto") public String go(HttpServletRequest request, Map<String,Object> map, Model model){ request.setAttribute("msg","传过来...."); map.put("

  • MyBatis框架底层的执行原理源码解析

    目录 1.前言 2.案例项目源码 3.MyBatis源码解析底层执行原理 3.1 读取mybatis配置文件创建出SqlSeesionFactory对象 3.2 通过SqlSeesionFactory对象进而创建出SqlSession对象 3.3 通过SqlSession的getMapper获取到接口代理对象 3.4 通过mapper接口的代理对象执行CRUD 1.前言 MyBatis框架大家肯定都用过的,废话我就不再多说了,这篇文章就给大家分享一下有关MyBatis框架底层的执行原理吧(Deb

  • React Hydrate原理源码解析

    目录 引言 Demo ReactDOM.render ReactDOM.hydrate hydrate 过程 事件绑定 hydrate 源码剖析 beginWork HostRoot Fiber HostComponent HostText Fiber tryToClaimNextHydratableInstance completeUnitOfWork popHydrationState prepareToHydrateHostInstance prepareToHydrateHostText

  • go slice 扩容实现原理源码解析

    目录 正文 扩容的示例 实际扩容倍数 growslice 实现 growslice 实现步骤 growslice 源码剖析 总结 正文 基于 Go 1.19. go 的切片我们都知道可以自动地进行扩容,具体来说就是在切片的容量容纳不下新的元素的时候, 底层会帮我们为切片的底层数组分配更大的内存空间,然后把旧的切片的底层数组指针指向新的内存中: 目前网上一些关于扩容倍数的文章都是基于相对旧版本的 Go 的,新版本中,现在切片扩容的时候并不是那种准确的小于多少容量的时候就 2 倍扩容, 大于多少容量

  • axios拦截器工作方式及原理源码解析

    目录 axios 拦截器的配置方式 use() 方法的定义 拦截器如何执行 拦截器回调方法的添加顺序 同步执行请求拦截器(顺序执行) 异步执行请求拦截器(同时执行) Q&A 拦截器是如何工作的 拦截器的执行顺序 同步&异步 axios 拦截器的配置方式 本文所用 axios 版本号为:1.3.2. axios 中有两种拦截器: axios.interceptors.request.use(onFulfilled, onRejected, options):配置请求拦截器. onFulfil

  • async-validator实现原理源码解析

    目录 async-validator 介绍 async-validator 基本使用 async-validator 源码分析 async-validator 源码-构造函数 async-validator 源码-validate方法 async-validator 源码-register方法 总结 最后 async-validator 介绍 async-validator是异步的验证数据是否合法有效的工具, 内置了不同数据类型的常见验证规则. 在需要对数据进行验证的场景中,都可以考虑使用asy

  • python装饰器原理源码示例分析

    目录 前言 一.什么是装饰器 二.为什么要用装饰器 三.简单的装饰器 四.装饰器的语法糖 五.装饰器传参 六.带参数的装饰器 七.类装饰器 八.带参数的类装饰器 九.装饰器的顺序 前言 最近有人问我装饰器是什么,我就跟他说,其实就是装饰器就是类似于女孩子的发卡.你喜欢的一个女孩子,她可以有很多个发卡,而当她戴上不同的发卡,她的头顶上就是装饰了不同的发卡.但是你喜欢的女孩子还是你喜欢的女孩子.如果还觉得不理解的话,装饰器就是咱们的手机壳,你尽管套上了手机壳,但并不影响你的手机功能,可你的手机还是该

  • Vue解读之响应式原理源码剖析

    目录 初始化 initState() initProps() initData() observe() Observer defineReactive() 依赖收集 Dep Watcher 依赖收集过程 移除订阅 派发更新 notify() update() queueWatcher() flushSchedulerQueue() updated() defineProperty 缺陷及处理 Vue.set() 重写数组方法 总结 先看张图,了解一下大体流程和要做的事 初始化 在 new Vue

  • Vue watch原理源码层深入讲解

    目录 添加依赖 触发依赖 总结 由于我在从源码看vue(v2.7.10)的computed的实现原理中详细的讲解过computed的实现,本篇跟computed的原理类似.我就带大家简单分析一下. 添加依赖 代码如下: <template> <div> {{a}} <button @click="addModule">新增</button> </div> </template> <script> exp

  • spring-session简介及实现原理源码分析

    一:spring-session介绍 1.简介 session一直都是我们做集群时需要解决的一个难题,过去我们可以从serlvet容器上解决,比如开源servlet容器-tomcat提供的tomcat-redis-session-manager.memcached-session-manager. 或者通过nginx之类的负载均衡做ip_hash,路由到特定的服务器上.. 但是这两种办法都存在弊端. spring-session是spring旗下的一个项目,把servlet容器实现的httpSe

随机推荐