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原理的资料请关注软件开发网其它相关文章!