async-validator實現(xiàn)原理源碼解析
async-validator 介紹
async-validator是異步的驗證數(shù)據(jù)是否合法有效的工具, 內(nèi)置了不同數(shù)據(jù)類型的常見驗證規(guī)則。
在需要對數(shù)據(jù)進行驗證的場景中,都可以考慮使用async-validator。 很多流行的組件庫的表單驗證都是基于async-validator的數(shù)據(jù)驗證實現(xiàn),如elementui、ant-design-vue、iview等
async-validator 基本使用
import AsyncValidator from 'async-validator';
// 1.聲明規(guī)則 descriptor
const descriptor = {
name: [
{
type: 'string',
required: true,
message: 'name 字段不能為空!'
},
// 通過調(diào)用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',
// 自定義驗證規(guī)則. age字段不能小于18, 小于則提示 ‘年紀太小'
validator: (rule, value, callback) => {
if (value < 18) {
// 通過執(zhí)行callback傳遞數(shù)據(jù)驗證的結果
callback('年紀太小');
} else {
callback();
}
},
},
};
// 2.創(chuàng)建async-validator實例
const validator = new AsyncValidator(descriptor);
// 3.數(shù)據(jù)源
const data = { name: 'muji', age: 16 }
// 4.執(zhí)行驗證
validator.validate(data, function(errors, fields) {
if (!errors) {
// 驗證成功
console.log('驗證通過');
} else {
// 驗證失敗
console.log('驗證不通過', error);
}
})
async-validator 源碼分析
從上面的基本使用中可以看到, 使用async-validator的過程主要分為:
1.創(chuàng)建async-validator實例
2.執(zhí)行實例的validate方法時,傳入數(shù)據(jù)源進行驗證
async-validator 源碼-構造函數(shù)
我們先分析第一步創(chuàng)建async-validator實例時,async-validator的構造函數(shù)做了什么事情。
constructor(descriptor: Rules) {
this.define(descriptor);
}
define(rules: Rules) {
// 規(guī)則配置不能為空
if (!rules) {
throw new Error('Cannot configure a schema with no rules');
}
// 規(guī)則配置必須是對象
if (typeof rules !== 'object' || Array.isArray(rules)) {
throw new Error('Rules must be an object');
}
this.rules = {};
// 統(tǒng)一字段的規(guī)則配置方式為數(shù)組形式。因為給字段配置驗證規(guī)則時, 可存在對象、數(shù)組的配置方式(如下).
/** 為name配置單條規(guī)則
* const descriptor = {
name: {
type: 'string',
required: true
}
}
也可以數(shù)組形式為name配置多條規(guī)則
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];
});
}
構造函數(shù)中只是執(zhí)行了define方法。
而define方法內(nèi)做了以下幾步:
1.驗證傳入的驗證規(guī)則是否合法。
2.統(tǒng)一字段的規(guī)則配置方式為數(shù)組形式
async-validator 源碼-validate方法
/** validate方法可接受三個參數(shù)
* source: 需要驗證的數(shù)據(jù)源.
* options:驗證參數(shù)(可選)
* callback:驗證完成回調(diào)(可選。validate會返回promise,因此可直接通過promise執(zhí)行驗證完成后的邏輯)
*/
validate(source: Values, options: any = {}, callback: any = () => {}): Promise<Values> {
/**
* 第一步
* 處理傳入?yún)?shù)。
* 如果options為函數(shù), 則將該函數(shù)設置為完成回調(diào),使第二個參數(shù)可直接傳遞callback, 方便調(diào)用者使用。
*/
if (typeof options === 'function') {
callback = options;
options = {};
}
// 此處省略了部分非核心邏輯代碼
// series保存最終的數(shù)據(jù)驗證的規(guī)則集合。
const series: Record<string, RuleValuePackage[]> = {};
/**
* 第二步
* 遍歷、處理rules中所有字段的驗證規(guī)則。(rules為構造函數(shù)中處理的處理保存的數(shù)據(jù))
*/
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驗證器函數(shù)(每個規(guī)則都必須存在一個validator函數(shù)去處理數(shù)據(jù)的驗證邏輯)
// getValidationMethod就是獲取該rule的validator驗證函數(shù)。
// 如果rule中存在自定義的validator配置,則直接返回。
// 如果不存在,則嘗試根據(jù)rule中的type數(shù)據(jù)類型獲取內(nèi)置的validator驗證函數(shù)
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的驗證規(guī)則,執(zhí)行每條規(guī)則的validator驗證函數(shù)進行數(shù)據(jù)的驗證。
* 然后asyncMap返回Promise. 可監(jiān)聽數(shù)據(jù)驗證結果
*/
return asyncMap(
series,
options,
(data, next) => {
// 每個規(guī)則的遍歷回調(diào)。處理每條規(guī)則,并且執(zhí)行規(guī)則中的驗證函數(shù)validator (下面分析函數(shù)內(nèi)具體邏輯)
},
errors => {
// 完成回調(diào)。所有規(guī)則處理完成后執(zhí)行的回調(diào)
},
source,
);
}
getValidationMethod 獲取規(guī)則的數(shù)據(jù)驗證函數(shù)源碼
getValidationMethod(rule: InternalRuleItem) {
// 存在自定義驗證函數(shù)直接返回
if (typeof rule.validator === 'function') {
return rule.validator;
}
// 省略部分非核心邏輯代碼
// 根據(jù)指定的類型,獲取對應的數(shù)據(jù)驗證函數(shù)
return validators[this.getType(rule)] || undefined;
}
// 根據(jù)規(guī)則配置項的配置,返回不同的類型
getType(rule: InternalRuleItem) {
// 不存在type類型, pattern為正則,則使用pattern類型
if (rule.type === undefined && rule.pattern instanceof RegExp) {
rule.type = 'pattern';
}
// 省略部分非核心邏輯代碼
// 規(guī)則配置項中存在type則返回, 不存在則返回string類型
return rule.type || 'string';
}
// async-validator中內(nèi)置的數(shù)據(jù)類型。
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驗證規(guī)則獲取具體驗證數(shù)據(jù)的validator驗證函數(shù)。
到第三步后,遍歷驗證規(guī)則series集合,執(zhí)行規(guī)則中的validator驗證函數(shù)時,把數(shù)據(jù)傳入validator函數(shù)中進行驗證。
第三步完整代碼
/**
* 遍歷series的驗證規(guī)則,執(zhí)行每條規(guī)則的validator驗證函數(shù)進行數(shù)據(jù)的驗證。
* 然后asyncMap返回Promise. 可監(jiān)聽數(shù)據(jù)驗證結果
*/
return asyncMap(
series,
options,
(data, next) => {
const rule = data.rule;
// 此處省略部分非核心邏輯
let res: ValidateResult;
/**
* 第一步
* 存在validator, 執(zhí)行validator驗證函數(shù),不同數(shù)據(jù)類型的validator驗證函數(shù),對數(shù)據(jù)的驗證邏輯不同
*/
if (rule.validator) {
/**
* 執(zhí)行validator驗證器函數(shù)
* rule: 規(guī)則
* data.value:需要驗證的值
* cb:validator執(zhí)行完成后, 通過cb函數(shù)處理驗證的結果,然后執(zhí)行下一個規(guī)則的驗證
* data.source:原始傳入的數(shù)據(jù)源
* options: 調(diào)用validate時傳遞的options
*/
res = rule.validator(rule, data.value, cb, data.source, options);
}
/**
* 第二步, 處理validator驗證的返回結果, validator函數(shù)內(nèi)部可以執(zhí)行傳遞的cb函數(shù)傳遞驗證的結果
*/
if (res === true) {
// validator返回true時,表示沒有錯誤,直接執(zhí)行cb進行下一個規(guī)則的驗證。
cb();
} else if (res instanceof Error) {
// validator返回Error時, 傳遞錯誤信息給cb函數(shù), cb函數(shù)記錄錯誤信息, 然后cb函數(shù)執(zhí)行下一個規(guī)則的驗證
cb(res.message);
} else if (res && (res as Promise<void>).then) {
/**
* validator驗證函數(shù)中,亦可通過返回Promise傳遞驗證的結果
* validator返回Promise時, 注冊Promise的成功、失敗回調(diào)
* 成功時:執(zhí)行cb函數(shù), 傳遞空, 表示不存在錯誤, 然后cb函數(shù)執(zhí)行下一個規(guī)則
* 失敗時: 執(zhí)行cb函數(shù), 傳遞錯誤信息, 然后cb函數(shù)執(zhí)行下一個規(guī)則
*/
(res as Promise<void>).then(
() => cb(),
e => cb(e),
);
}
/**
* validator驗證函數(shù)驗證完成后,需要執(zhí)行cb函數(shù),進行驗證結果的處理、記錄
* 并調(diào)用next使asyncMap執(zhí)行下一個規(guī)則的驗證
*/
function cb(e: SyncErrorType | SyncErrorType[] = []) {
let errorList = Array.isArray(e) ? e : [e];
if (errorList.length && rule.message !== undefined) {
errorList = [].concat(rule.message);
}
/**
* complementError中會為錯誤信息項填充額外的信息。如出現(xiàn)錯誤的字段、出現(xiàn)錯誤的值
*/
let filledErrors = errorList.map(complementError(rule, source));
// asyncMap并不是同步循環(huán)series規(guī)則集合,而是遍歷的過程中,需要等待執(zhí)行next才會遍歷下一個series中的規(guī)則
// 將錯誤結果filledErrors傳遞到下一個規(guī)則的事件循環(huán)中,最后所有規(guī)則驗證完成時,能夠獲取到所有的規(guī)則的驗證結果
next(filledErrors);
}
},
// errors 即所有驗證不通過的錯誤記錄(即執(zhí)行next時傳遞的所有filledErrors)
errors => {
// 所有規(guī)則處理完成后執(zhí)行的回調(diào)
let fields: ValidateFieldsError = {};
if (!errors.length) {
// 不存在錯誤, 直接執(zhí)行validate時傳遞的完成回調(diào)
callback(null, source);
} else {
// 存在錯誤
// 將errors錯誤記錄按字段分類, 如每個字段可配置多條規(guī)則, 因此每個字段可能存在多個錯誤記錄
// fields 數(shù)據(jù)格式如 { field1: [error1, error2], field2: [error1] }
fields = convertFieldsError(errors);
// 執(zhí)行完成回調(diào), 傳遞errors錯誤記錄, fields錯誤記錄分類
(callback as (
errors: ValidateError[],
fields: ValidateFieldsError,
) => void)(errors, fields);
}
},
source,
);
以上代碼主要分為以下幾步:
1.遍歷驗證的規(guī)則集合
2.執(zhí)行每條規(guī)則的validator驗證函數(shù),進行數(shù)據(jù)驗證。
3.驗證完成后, 執(zhí)行cb函數(shù)處理、記錄驗證的結果,然后cb執(zhí)行next處理下一條規(guī)則。
4.所有規(guī)則遍歷處理完后,觸發(fā)調(diào)用validate時傳入的callback,并傳入驗證結果。
async-validator 源碼-register方法
在validators中注冊新的validator數(shù)據(jù)驗證器。
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數(shù)據(jù)驗證器函數(shù)添加到validators中
// 后續(xù)執(zhí)行數(shù)據(jù)驗證時,會根據(jù)type在validators中取驗證器對數(shù)據(jù)進行驗證
validators[type] = validator;
};
總結
async-validator可以分為兩個部分。
1.validators驗證器集合: 保存著不同type數(shù)據(jù)類型的驗證函數(shù)??梢酝ㄟ^register對validators進行擴展。
2.validate方法: 為rule規(guī)則根據(jù)type數(shù)據(jù)類型在validators驗證器集合中匹配對應的validator函數(shù)進行數(shù)據(jù)驗證。大致的執(zhí)行過程如下
- 遍歷外部傳入的規(guī)則配置項,根據(jù)配置項中的type數(shù)據(jù)類型,獲取對應數(shù)據(jù)類型的validator驗證函數(shù),得到最終的驗證規(guī)則集合。
- 遍歷最終的規(guī)則集合,執(zhí)行規(guī)則的validator驗證函數(shù), 處理、收集驗證函數(shù)的驗證結果。
- 所有規(guī)則執(zhí)行完成后,觸發(fā)外部傳遞的callback完成函數(shù),并且傳遞收集到的驗證結果。
最后
async-validator中非核心流程的部分經(jīng)過了省略。
以上只是我對async-validator的一點理解,希望我們能一起學習、一起進步。
最后,你可以從功能的實現(xiàn)、代碼的組織、可讀性等任何的角度思考下async-validator中做得比較好或者能夠優(yōu)化的地方嗎?更多關于async-validator原理的資料請關注腳本之家其它相關文章!
相關文章
JavaScript中isPrototypeOf函數(shù)
這篇文章主要介紹了JavaScript中isPrototypeOf函數(shù),isPrototypeOf() 是 Object函數(shù)(類)的下的一個方法,用于判斷當前對象是否為另外一個對象的原型,如果是就返回 true,否則就返回 false,下面來看看詳細內(nèi)容,需要的朋友可以參考一下2021-11-11
JavaScript生成器函數(shù)Generator?Functions優(yōu)缺點特性詳解
這篇文章主要為大家介紹了JavaScript生成器函數(shù)Generator?Functions的特性及優(yōu)點詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-05-05
webpack5之output和devServer的publicPath區(qū)別示例詳解
這篇文章主要為大家介紹了webpack5之output和devServer的publicPath區(qū)別示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12

