async-validator实现原理源码解析
作者:会说话的番茄
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原理的资料请关注脚本之家其它相关文章!