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