Element使用的async-validator表單校驗庫源碼超詳細解析

大家好,我是若川。持續組織了8個月源碼共讀活動,感興趣的可以?點此加我微信ruochuan12?參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》?包含20余篇源碼文章。歷史面試系列。另外:目前建有江西|湖南|湖北籍前端群,可加我微信進群。


平常開發寫 element 表單的時候,肯定少不了表單的校驗,element 使用的是 async-validator 這個開源庫。

44ab91e5ead0f352cafea54c5d19d373.png
image-20220517090312952

這篇文章詳細分析一下 async-validator 的主流程。

使用方法

import?Schema?from?'async-validator';
const?descriptor?=?{list:?{required:?true,type:?'number',},limit:?[{required:?true,message:?'數量必填',},{validator(r,?v,?cb)?{if?(v?<?100)?{return?cb(new?Error('數量不能小于?100'));}cb();},},],
};
const?validator?=?new?Schema(descriptor);
validator.validate({?list:?'12',?limit:?null?},{?firstFields:?true?},(errors,?fields)?=>?{if?(errors)?{console.log('錯誤列表',?errors);}},
);

我們需要定義 descriptor ,也就是我們在 element 中定義的 rules ,然后創建一個 Schema 對象。

最后調用 validate 函數,傳遞三個參數:

第一個參數是要校驗的對象

第二個參數是 options 對象, firstFieldstrue ,表示同一個字段如果有多個校驗規則,一旦出現校驗不通過的規則后邊的規則就不執行了。

還可以設置 firsttrue,這個是針對整個校驗對象的,如果某個字段校驗不通過,那么后邊所有的字段就不再校驗了。

第三個參數是校驗結束后的回調函數,erros 保存了所有校驗失敗的字段以及 message 信息。

因此,上邊代碼的輸出如下:

58af70b427694d197be4032df2966133.png
image-20220519081546339

list 對應結果的 message 是默認為我們添加的,limit 對應結果的 message 是我們自己設置的,會覆蓋默認的 message

因為我們設置了 firstFieldstrue ,所以只校驗了 limit 的第一個規則,第二個規則就沒有走到。

我們給 limit 設置一個值,讓它走到第二個校驗規則。

validator.validate({?list:?'12',?limit:?3?},{?firstFields:?true?},(errors,?fields)?=>?{if?(errors)?{console.log('錯誤列表',?errors);}},
);

輸出如下:

6ead22d9ac6a7437d0f05238ed1731c1.png
image-20220519081840212

此時 limit 對應結果就是一個 Error 對象了,Error 對象除了本身的 message 屬性,默認還為我們添加了 fieldfiledValue 屬性。

預處理 descriptor

校驗前 ?async-validator ?會將傳入的 descriptor 規范化。

我們傳進入的是下邊的樣子:

const?descriptor?=?{list:?{required:?true,type:?'number',},limit:?[{required:?true,message:?'數量必填',},{validator(r,?v,?cb)?{if?(v?<?100)?{return?cb(new?Error('數量不能小于?100'));}cb();},},],
};

預處理后會變成下邊的樣子:

{list:?[{rule:?{required:?true,type:?'number',field:?'list',fullField:?'list',validator:?(rule,?value,?callback,?source,?options)?=>?{const?errors?=?[];const?validate?=rule.required?||(!rule.required?&&?source.hasOwnProperty(rule.field));if?(validate)?{if?(value?===?'')?{value?=?undefined;}if?(isEmptyValue(value)?&&?!rule.required)?{return?callback();}rules.required(rule,?value,?source,?errors,?options);if?(value?!==?undefined)?{rules.type(rule,?value,?source,?errors,?options);rules.range(rule,?value,?source,?errors,?options);}}callback(errors);},},value:?'12',source:?{list:?'12',limit:?3,},field:?'list',},],limit:?[{rule:?{required:?true,message:?'數量必填',field:?'limit',fullField:?'limit',type:?'string',validator:?(rule,?value,?callback,?source,?options)?=>?{const?errors?=?[];const?type?=?Array.isArray(value)???'array'?:?typeof?value;rules.required(rule,?value,?source,?errors,?options,?type);callback(errors);},},value:?3,source:?{list:?'12',limit:?3,},field:?'limit',},{rule:?{field:?'limit',fullField:?'limit',type:?'string',validator(r,?v,?cb)?{if?(v?<?100)?{return?cb(new?Error('數量不能小于?100'));}cb();},},value:?3,source:?{list:?'12',limit:?3,},field:?'limit',},],
};

主要做了三件事情:

  1. 把每個字段的校驗規則統一成了一個數組對象

  2. 把原本的校驗對象放到了 rule 屬性中,并且添加了 valuesourcefield 屬性

  3. 根據 requiredtype 補充了默認的 validator 校驗函數

預處理 descriptor 對應的源碼

讓我們過一下這部分源碼。

在構造函數中,把 descriptor 所有字段的 rule 轉為了數組,保存到 rules 對象中。

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?=?{};Object.keys(rules).forEach(name?=>?{const?item:?Rule?=?rules[name];this.rules[name]?=?Array.isArray(item)???item?:?[item];});
}

剩下的處理都在 validate 函數中了,可以跟隨下邊的注釋看一下:

validate(source_:?Values,?o:?any?=?{},?oc:?any?=?()?=>?{}):?Promise<Values>?{let?source:?Values?=?source_;let?options:?ValidateOption?=?o;let?callback:?ValidateCallback?=?oc;if?(typeof?options?===?'function')?{callback?=?options;options?=?{};}...function?complete(results:?(ValidateError?|?ValidateError[])[])?{...}const?series:?Record<string,?RuleValuePackage[]>?=?{};const?keys?=?options.keys?||?Object.keys(this.rules);?//?得到所有的要校驗的?keykeys.forEach(z?=>?{?//?遍歷所有字段const?arr?=?this.rules[z];let?value?=?source[z];arr.forEach(r?=>?{?//?遍歷每個字段的所有?rulelet?rule:?InternalRuleItem?=?r;...//?如果是函數,放到?validator?屬性中if?(typeof?rule?===?'function')?{rule?=?{validator:?rule,};}?else?{rule?=?{?...rule?};}//?填充?validator?屬性rule.validator?=?this.getValidationMethod(rule);if?(!rule.validator)?{return;}//?填充其他屬性rule.field?=?z;rule.fullField?=?rule.fullField?||?z;rule.type?=?this.getType(rule);series[z]?=?series[z]?||?[];//?保存到?series?中series[z].push({rule,value,source,field:?z,});});});const?errorFields?=?{};
}

看下上邊的 getValidationMethod 方法:

getValidationMethod(rule:?InternalRuleItem)?{//?如果用戶自定了,直接返回自定義的if?(typeof?rule.validator?===?'function')?{return?rule.validator;}const?keys?=?Object.keys(rule);const?messageIndex?=?keys.indexOf('message');if?(messageIndex?!==?-1)?{keys.splice(messageIndex,?1);}//?如果只有一個?required?字段,返回?required?的校驗函數if?(keys.length?===?1?&&?keys[0]?===?'required')?{return?validators.required;}//?否則的根據?type?去返回校驗函數return?validators[this.getType(rule)]?||?undefined;
}

所有的校驗函數都是提前定義好的:

5597dcb9285d7037c57dde1871ed922b.png
image-20220519094003441

在 前端的設計模式中-策略模式 中我們也提到過上邊的邏輯。

循環校驗

當我們有了預處理好的所有字段的校驗規則。

const?series?=?{list:?[{rule:?{required:?true,type:?'number',field:?'list',fullField:?'list',validator:?(rule,?value,?callback,?source,?options)?=>?{const?errors?=?[];const?validate?=rule.required?||(!rule.required?&&?source.hasOwnProperty(rule.field));if?(validate)?{if?(value?===?'')?{value?=?undefined;}if?(isEmptyValue(value)?&&?!rule.required)?{return?callback();}rules.required(rule,?value,?source,?errors,?options);if?(value?!==?undefined)?{rules.type(rule,?value,?source,?errors,?options);rules.range(rule,?value,?source,?errors,?options);}}callback(errors);},},value:?'12',source:?{list:?'12',limit:?3,},field:?'list',},],limit:?[{rule:?{required:?true,message:?'數量必填',field:?'limit',fullField:?'limit',type:?'string',validator:?(rule,?value,?callback,?source,?options)?=>?{const?errors?=?[];const?type?=?Array.isArray(value)???'array'?:?typeof?value;rules.required(rule,?value,?source,?errors,?options,?type);callback(errors);},},value:?3,source:?{list:?'12',limit:?3,},field:?'limit',},{rule:?{field:?'limit',fullField:?'limit',type:?'string',validator(r,?v,?cb)?{if?(v?<?100)?{return?cb(new?Error('數量不能小于?100'));}cb();},},value:?3,source:?{list:?'12',limit:?3,},field:?'limit',},],
};

接下來只需要搞一個雙重循環,執行所有的字段和每個字段的所有校驗函數。

for(const?field?of?Object.keys(series))?{?//?遍歷每一個字段for(const?data?of?series[field])?{?//?每一個規則const?rule?=?data.rule;const?res?=?rule.validator(rule,?data.value,?cb,?data.source,?options);}
}

ruledata.valuedata.source 就是當前規則相關的變量,options 是最開始調用校驗的時候傳進來的 { firstFields: true },,那么 cb 是什么?

cb 函數接受一個錯誤數據列表,如果返回的不是數組會包裝為數組,然后對錯誤進行填充。

最后調用 doIt 函數,將校驗結果傳入,后邊會介紹這個方法。

function?cb(e?=?[])?{let?errorList?=?Array.isArray(e)???e?:?[e];if?(errorList.length?&&?rule.message?!==?undefined)?{errorList?=?[].concat(rule.message);?//?錯誤列表優先使用?message?字段}//?Fill?error?infolet?filledErrors?=?errorList.map(complementError(rule,?source));doIt(filledErrors);?//?將當前字段的錯誤列表保存起來
}

complementError 會返回一個函數,將錯誤列表進行填充,主要就是補充了 fieldfieldValue 屬性。

export?function?complementError(rule:?InternalRuleItem,?source:?Values)?{return?(oe:?ValidateError?|?(()?=>?string)?|?string):?ValidateError?=>?{let?fieldValue;if?(rule.fullFields)?{fieldValue?=?getValue(source,?rule.fullFields);}?else?{fieldValue?=?source[(oe?as?any).field?||?rule.fullField];}if?(isErrorObj(oe))?{oe.field?=?oe.field?||?rule.fullField;oe.fieldValue?=?fieldValue;return?oe;}return?{message:?typeof?oe?===?'function'???oe()?:?oe,fieldValue,field:?((oe?as?unknown)?as?ValidateError).field?||?rule.fullField,};};
}

收到的錯誤列表分為兩種情況:

處理前如果 cb 收到的是 Error 列表,比如這樣調用 cb(new Error('數量不能小于 100'));

那么處理前是下圖:

c422ff004019d1fdf408f8ca30222e5f.png
image-20220521082112809

處理后,就會往 Error 對象中塞入 fieldfieldValue 屬性。

b0c58b044064be1f390b8366d35f4004.png
image-20220521082219018

處理前如果cb 是字符串列表,比如這樣調用 cb(['list is required', 'list is not a number'])

318f8a45820ae0482d08823f91008e24.png
image-20220521082345013

同樣的,處理后也是塞入 fieldfieldValue 屬性。

8e9db932fa9ff02fdb5658b9196cb2c0.png
image-20220521082457313

再回到我們的雙重循環中。

for(const?field?of?Object.keys(series))?{?//?遍歷每一個字段for(const?data?of?series[field])?{?//?每一個規則const?rule?=?data.rule;const?res?=?rule.validator(rule,?data.value,?cb,?data.source,?options);}
}

其中 validator 函數就是我們自己定義的:

validator(r,?v,?cb)?{if?(v?<?100)?{return?cb(new?Error('數量不能小于?100'));}cb();
},

由于 Element 官方示例是上邊的樣子,所以我們一般都按照上邊的樣子寫,但其實我們也可以不調用 cb 函數,而是僅僅 return 字符串數組,或者 boolean 值,調用 cb 函數交給雙重循環。

validator(r,?v,?cb)?{if?(v?<?100)?{return?'數量不能小于?100'}return?true;
},

雙重循環中來處理 validator 的返回值去調用 cb 函數。

for(const?field?of?Object.keys(series))?{?//?遍歷每一個字段for(const?data?of?series[field])?{?//?每一個規則const?rule?=?data.rule;const?res?=?rule.validator(rule,?data.value,?cb,?data.source,?options);//?根據返回的結果,去調用?cb?函數if?(res?===?true)?{cb();}?else?if?(res?===?false)?{cb(typeof?rule.message?===?'function'??rule.message(rule.fullField?||?rule.field):?rule.message?||?`${rule.fullField?||?rule.field}?fails`,);}?else?if?(res?instanceof?Array)?{cb(res);}?else?if?(res?instanceof?Error)?{cb(res.message);}}
}

asyncMap

向上邊我們直接粗暴的寫雙重循環去依次校驗也沒有問題,但因為校驗庫還支持一些參數,比如前邊介紹的:

2755a0d0e8ff1874b112dd6d662bce47.png
image-20220521083746778

如果是 for 循環中去處理 firstFieldsfirst 的邏輯,就過于耦合了,未來再擴充其他邏輯,雙重循環中的邏輯就會越來越復雜。

async-validator ?的處理方式在這里就比較優雅了,實現了 asyncMap 方法,作用就是遍歷 series 數組,并且處理了 firstFieldsfirst 參數的邏輯。

下邊來分析一下實現:

看一下 asyncMap 的入口參數。

export?function?asyncMap(objArr:?Record<string,?RuleValuePackage[]>,option:?ValidateOption,func:?ValidateFunc,callback:?(errors:?ValidateError[])?=>?void,source:?Values,
){}

接受 5 個參數:

objArr:要遍歷的 rule 規則,就是我們前邊生成的 series 數組,即雙重循環遍歷的對象。

option :最開始傳入的 option,可能包含 firstFieldsfirst 屬性。

func:遍歷過程的中會調用這個函數,會傳入當前遍歷的 rule 和一個 doIt 函數,doIt 函數需要接收處理好的校驗結果。這里就需要我們之前 for 循環內部的處理邏輯。

callback : 全部檢驗結束后調用,會傳入所有的校驗結果。

source:要校驗的對象。

這樣我們就可以把 for 循環改為直接調用 asyncMap 函數了。

asyncMap(series,options,(data,?doIt)?=>?{...},results?=>?{complete(results);},source,
);

第三個參數就是需要我們去處理 data 這個校驗規則,也就是之前 for 循環中的邏輯移動過來。

其中 doIt 函數我們在之前講的 cb 函數中調用即可。

(data,?doIt)?=>?{const?rule?=?data.rule;rule.field?=?data.field;function?cb(e:?SyncErrorType?|?SyncErrorType[]?=?[])?{let?errorList?=?Array.isArray(e)???e?:?[e];if?(errorList.length?&&?rule.message?!==?undefined)?{errorList?=?[].concat(rule.message);}//?Fill?error?infolet?filledErrors?=?errorList.map(complementError(rule,?source));doIt(filledErrors);?//?將當前字段的錯誤列表保存起來}/********?for?循環中的邏輯?*****************/const?res?=?rule.validator(rule,?data.value,?cb,?data.source,?options);if?(res?===?true)?{cb();}?else?if?(res?===?false)?{cb(typeof?rule.message?===?'function'??rule.message(rule.fullField?||?rule.field):?rule.message?||?`${rule.fullField?||?rule.field}?fails`,);}?else?if?(res?instanceof?Array)?{cb(res);}?else?if?(res?instanceof?Error)?{cb(res.message);}/***************************************/
},

最后就是全部遍歷結束后的 complete 函數,我們只需要把 results 列表傳到外邊即可。

function?complete(results)?{let?fields:?ValidateFieldsError?=?{};if?(!results.length)?{callback(null,?source);}?else?{fields?=?convertFieldsError(results);callback?(results,?fields);}
}

上邊的 callback 函數就是我們調用校驗函數時候外部傳入的:

const?validator?=?new?Schema(descriptor);
validator.validate({?list:?'12',?limit:?null?},{?firstFields:?true?},//*****?上邊的?callback?********************/(errors,?fields)?=>?{if?(errors)?{console.log('錯誤列表',?errors);}},//*********************************************/
);

內層循環

雙重循環的的外層是遍歷所有字段,內層是遍歷該字段的所有規則。

我們來先看一下內層循環的實現:

async-validator ?庫提供了 asyncParallelArray 方法。

function?asyncParallelArray(arr:?RuleValuePackage[],func:?ValidateFunc,callback:?(errors:?ValidateError[])?=>?void,
)?{const?results:?ValidateError[]?=?[];let?total?=?0;const?arrLength?=?arr.length;function?count(errors:?ValidateError[])?{results.push(...(errors?||?[]));total++;if?(total?===?arrLength)?{callback(results);}}arr.forEach(a?=>?{func(a,?count);});
}

接受三個參數:

arr 就是當前字段要遍歷的規則列表。

func 是處理 rule 規則的函數,內部會調用這里的 count 方法,接受當前 a 的校驗結果。

傳入的 func 其實就是我們前邊介紹過的 for 循環內部邏輯,a 是下邊的 data 參數,count 就是下邊的 doIt

(data,?doIt)?=>?{const?rule?=?data.rule;rule.field?=?data.field;function?cb(e:?SyncErrorType?|?SyncErrorType[]?=?[])?{let?errorList?=?Array.isArray(e)???e?:?[e];if?(errorList.length?&&?rule.message?!==?undefined)?{errorList?=?[].concat(rule.message);}//?Fill?error?infolet?filledErrors?=?errorList.map(complementError(rule,?source));doIt(filledErrors);}const?res?=?rule.validator(rule,?data.value,?cb,?data.source,?options);if?(res?===?true)?{cb();}?else?if?(res?===?false)?{cb(typeof?rule.message?===?'function'??rule.message(rule.fullField?||?rule.field):?rule.message?||?`${rule.fullField?||?rule.field}?fails`,);}?else?if?(res?instanceof?Array)?{cb(res);}?else?if?(res?instanceof?Error)?{cb(res.message);}
},

第三個參數 callback 是當前 arr 全部校驗結束后的回調,代表當前字段的所有校驗規則都判斷結束。

這里需要注意的是,我們是通過 count 進入的次數來判斷是否去調用 callback 函數,而不是 arr 遍歷結束后調用 callback

除了 asyncParallelArray 方法,因為有 firstFields 屬性的存在,也就是遍歷某個字段的所有規則時,如果出現校驗不通過的規則就直接結束,后邊的規則不再進行判斷。

因此, async-validator ?還提供了 asyncSerialArray 方法。

function?asyncSerialArray(arr:?RuleValuePackage[],func:?ValidateFunc,callback:?(errors:?ValidateError[])?=>?void,
)?{let?index?=?0;const?arrLength?=?arr.length;function?next(errors:?ValidateError[])?{if?(errors?&&?errors.length)?{callback(errors);return;}const?original?=?index;index?=?index?+?1;if?(original?<?arrLength)?{func(arr[original],?next);}?else?{callback([]);}}next([]);
}

入口參數和 asyncParallelArray 是一致的,區別在于對于 arr 是順序執行,如果過程中出現了校驗不通過的規則,就直接調用 callback 結束。

外層循環

外層循環和上邊很類似,其實就是遍歷所有字段,然后把每個字段的校驗列表傳給內層循環即可。

export?function?asyncMap(objArr:?Record<string,?RuleValuePackage[]>,option:?ValidateOption,func:?ValidateFunc,callback:?(errors:?ValidateError[])?=>?void,source:?Values,
)?{const?firstFields?=option.firstFields?===?true??Object.keys(objArr):?option.firstFields?||?[];const?objArrKeys?=?Object.keys(objArr);const?objArrLength?=?objArrKeys.length;let?total?=?0;const?results:?ValidateError[]?=?[];const?next?=?(errors:?ValidateError[])?=>?{results.push.apply(results,?errors);total++;if?(total?===?objArrLength)?{callback(results);}};if?(!objArrKeys.length)?{callback(results);}objArrKeys.forEach(key?=>?{const?arr?=?objArr[key];if?(firstFields.indexOf(key)?!==?-1)?{asyncSerialArray(arr,?func,?next);}?else?{asyncParallelArray(arr,?func,?next);}});
}

入口參數前邊已經介紹過了,可以看到我們做的就是遍歷 objArrKeys 數組,然后根據 firstFields 的值去調用 asyncSerialArrayasyncParallelArray 。內存循環判斷結束后會調用上邊的 next 方法。

next 同樣也是通過進入的次數,來判斷是否調用 callback 函數,也就是前邊介紹的 complete 方法。

和內層循環類似,因為有 first 屬性的存在,也就是遍歷某個字段時,存在校驗不通過的字段就直接結束,后邊的字段就不再進行判斷。

我們只需要把所有規則打平,然后調用 asyncSerialArray 方法即可。

if?(option.first)?{const?next?=?(errors:?ValidateError[])?=>?{callback(errors);};const?flattenArr?=?flattenObjArr(objArr);asyncSerialArray(flattenArr,?func,?next);
}function?flattenObjArr(objArr:?Record<string,?RuleValuePackage[]>)?{const?ret:?RuleValuePackage[]?=?[];Object.keys(objArr).forEach(k?=>?{ret.push(...(objArr[k]?||?[]));});return?ret;
}

代碼總

以上就是 ?async-validator 源碼的主要流程了,說起來也簡單,先預處理所有規則,然后通過 asyncMap 方法雙層循環遍歷所有校驗規則即可,這個雙層循環的抽離確實很優雅,避免了循環中耦合太多邏輯。

除了上邊介紹的代碼,因為 async-validator 還支持 Promise 的調用風格,校驗函數支持 Promise 函數等其他功能,大家感興趣也可以到 async-validator 看一下更詳細的源碼。

值得一提的點是,雙層循環是通過計數來判斷是否結束的,而進入計數其實就是調用 cb 函數。因此如果我們規則是下邊的樣子:

import?Schema?from?'../src/index';
const?descriptor?=?{limit:?[{validator(r,?v,?cb)?{if?(v?<?100)?{cb('校驗1');}cb();},},{validator(r,?v,?cb)?{if?(v?<?50)?{return?cb('校驗2');}cb();},},],
};
const?validator?=?new?Schema(descriptor);
validator.validate({?limit:?3?},(errors,?fields)?=>?{if?(errors)?{console.log('錯誤列表',?errors);}},
);

因為我們沒有傳遞 firstFields 屬性,所以我們期望的是將 limit 所有的校驗都進行了,limit 的值是 3 ,所以兩個校驗都沒通過,應該輸出下邊的內容:

bb94535a53c31b93bd5316079cb81adf.png
image-20220521103342907

但其實只進行了第一個的校驗:

548316e416b2bc2a985a316151e13cba.png
image-20220521103508233

原因就在于第一個 validator 進行了兩次 cb ,然后內層循環的 callback 就提前調用了。

validator(r,?v,?cb)?{if?(v?<?100)?{cb('校驗1');}cb();
},

因此我們最好保證一個 validator 只進行一次 cb ,走到 cb 后就直接 return。(因為 Element 會設置 firstFieldstrue,所以其實有多個 cb 也不影響最終結果)

validator(r,?v,?cb)?{if?(v?<?100)?{return?cb('校驗1');}cb();
},

并且一定要有一個 cb ,不然最終的回調函數永遠也不會執行了,這就是為什么 Element 提示我們要進行 cb

a8a841b22904bf5b3e780702155d43a1.png
image-20220521103841307

但這里說的也不夠嚴謹,我們也可以返回字符串,或者字符串數組、布爾值等, async-validator 內部會根據 validator 返回的結果去調用 cb 函數。

const?res?=?rule.validator(rule,?data.value,?cb,?data.source,?options);if?(res?===?true)?{cb();}?else?if?(res?===?false)?{cb(typeof?rule.message?===?'function'??rule.message(rule.fullField?||?rule.field):?rule.message?||?`${rule.fullField?||?rule.field}?fails`,);}?else?if?(res?instanceof?Array)?{cb(res);}?else?if?(res?instanceof?Error)?{cb(res.message);}

async-validator ?用計數的方式來判斷是否去調用回調,就是為了實現異步的校驗,當異步過程結束后才去調用 cb ,代表校驗完成。

其他屬性

平時寫代碼直接參照前人的校驗規則去仿照著寫了,大家也基本上是按照 Element 的樣例來寫校驗規則,如果去 ?async-validator ?看一下的話,會發現一些其他沒聽過的屬性,這里也記錄下。

validator 校驗函數最多能接收到 5 個參數。

validator(rule,?value,?callback,?source,?options)?{const?errors?=?[];//?test?if?email?address?already?exists?in?a?database//?and?add?a?validation?error?to?the?errors?array?if?it?doesreturn?errors;
},

我們可以通過第四個參數 source 拿到整個表單的對象,如果想校驗一些聯動的邏輯,我們就可以通過 source 拿到其他字段的值。

對對象字段的校驗,如果校驗字段是個對象,我們可以通過 fields 來校驗對象中的字段。

const?descriptor?=?{address:?{type:?'object',required:?true,fields:?{street:?{?type:?'string',?required:?true?},city:?{?type:?'string',?required:?true?},zip:?{?type:?'string',?required:?true,?len:?8,?message:?'invalid?zip'?},},},name:?{?type:?'string',?required:?true?},
};
const?validator?=?new?Schema(descriptor);
validator.validate({?address:?{}?},?(errors,?fields)?=>?{//?errors?for?address.street,?address.city,?address.zip
});

transform 函數,可以將值先進行一次轉換,然后再進行校驗。

const?descriptor?=?{name:?{type:?'string',required:?true,pattern:?/^[a-z]+$/,transform(value)?{return?value.trim();},},
};

asyncValidator,校驗函數內部是用 Promise 或者直接返回一個 Promise

const?fields?=?{asyncField:?{asyncValidator(rule,?value,?callback)?{ajax({url:?'xx',value:?value,}).then(function(data)?{callback();},?function(error)?{callback(new?Error(error));});},},promiseField:?{asyncValidator(rule,?value)?{return?ajax({url:?'xx',value:?value,});},},
};

上邊就是 ? async-validator ?開源庫的核心源碼了,希望對你有幫助。

e685564796777c58093383ad967b37a0.gif

·················?若川簡介?·················

你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》20余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經堅持寫了8年,點擊查看年度總結。
同時,最近組織了源碼共讀活動,幫助4000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。

e4ae600c8d0b96ee2a90c13303753e25.png

掃碼加我微信 ruochuan12、拉你進源碼共讀

今日話題

目前建有江西|湖南|湖北?籍 前端群,想進群的可以加我微信 ruochuan12?進群。分享、收藏、點贊、在看我的文章就是對我最大的支持~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/274637.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/274637.shtml
英文地址,請注明出處:http://en.pswp.cn/news/274637.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

從零手寫 Vue 之響應式系統

大家好&#xff0c;我是若川。持續組織了8個月源碼共讀活動&#xff0c;感興趣的可以 點此加我微信ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系列。另外…

WPF 分頁控件應用

效果圖&#xff1a; 前臺代碼&#xff1a; <UserControl x:Class"Layout.UI.Comm.Pager"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:mc"http:/…

李寧品牌重塑_邁伊多品牌重塑的幕后

李寧品牌重塑This post was originally published on the Maido blog.這篇文章最初發表在 Maido博客上 。 You might notice that we’ve had a little facelift at Maido. Or you might not — and that’s totally fine. What we launched at the end of last year was not r…

搭建前端監控,如何采集異常數據?

大家好&#xff0c;我是若川。持續組織了近一年的源碼共讀活動&#xff0c;感興趣的可以 點此加我微信ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系列。…

產品經理如何提高創造力_如何提高產品設計師的創造力

產品經理如何提高創造力When David Kelley, Bill Moggridge, and Mike Nuttall founded IDEO, a consulting firm that would become one of the most innovative companies of the late 90s, they brought a new perspective in product development.當大衛凱利(David Kelley)…

Github上8個很棒的Vue項目

大家好&#xff0c;我是若川。持續組織了近一年的源碼共讀活動&#xff0c;感興趣的可以 點此加我微信ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系列。…

域名解析文件hosts文件是什么?如何修改hosts文件?

如何修改hosts文件&#xff1f; hosts文件的位置&#xff1a;xp,2000等系統在 C:\windows\system32\drivers\etc 文件夾中找到Hosts文件并用記事本打開(Windows 9x/Me系統在C:\Windows文件夾中找)按照 ip地址 域名 的格式添加單獨的一行記錄。例如72.14.219.190 www.hbcms.net…

python 投資組合_成功投資組合的提示

python 投資組合Lately, I’ve had some free time during my job transition and have been reviewing a few of my friends’ design portfolios. Gradually, I found some common themes around the feedback I’ve given. And it occurred to me that others might find so…

Github上8個很棒的React項目

大家好&#xff0c;我是若川。持續組織了近一年的源碼共讀活動&#xff0c;感興趣的可以 點此加我微信ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系列。…

騰訊的筆試題一道

搜羅了一些騰訊的筆試題目 題目是這樣的&#xff1a;在如下8*6的矩陣中&#xff0c;請計算從A移動到B一共有多少種走法&#xff1f;要求每次只能向上揮著向右移動一格&#xff0c;并且不能經過P&#xff1b; B P …

屏幕廣播系統_如何設計系統,而不是屏幕

屏幕廣播系統重點 (Top highlight)Over the past several decades, rapid advances in technology have dramatically enhanced the digital customer experience and their expectations. In the face of these heightened customer expectations, the role of the Interactio…

Umi 4 發布啦

大家好&#xff0c;我是若川。持續組織了近一年的源碼共讀活動&#xff0c;感興趣的可以 點此加我微信ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系列。…

Win32匯編--加載菜單資源

基本上的窗口都會有一個菜單,現在就來看看Win32匯編中是如何加載菜單的: 1>在工程中添加新的菜單資源 2>雙擊新添加的菜單資源進行編輯 3>菜單欄:Make->Compile RC來編譯資源文件 4>導出資源中的ID號并寫到數據段的.const中 5>下面是完整的源代碼供參考:(工程…

Futura:從納粹主義到月球-甚至更遠

Reading the title of this article, the first thing that will come to mind for some is the funny expression of Buzz Lightyear — the Disney character — when he stretches his arms outwards and utters the famous phrase “To infinity and beyond!” before jump…

如何碎片化時間高效學習前端~

前端技術日新月異&#xff0c;發展迅速&#xff0c;作為一個與時俱進的前端工程師&#xff0c;需要不斷的學習。這里強烈推薦幾個前端開發工程師必備的優質公眾號&#xff0c;希望對你有所幫助。大家可以像我一樣&#xff0c;利用碎片時間閱讀這些公眾號的文章。前端從進階到入…

爬取淘寶定價需要多久時間_如何對設計工作進行定價—停止收??取時間并專注于價值

爬取淘寶定價需要多久時間Pricing creative work is a new concept for most freelancers who are starting their business. We are used to being paid for our time, either by an hourly wage or an annual salary. It makes it simple to quantify how much value we thin…

OEA 框架中集成的 RDLC 報表介紹

之前 OEA 一直用著一個 Delphi 開發的報表&#xff0c;所以兩年來我一直就想在 OEA 中構建一個純 .NET 的報表模塊&#xff0c;但是一想到要開發復雜的報表引擎和設計器就覺得麻煩。所以這事一直拖著。最近開始研究一些成熟的報表引擎&#xff0c;經過對比&#xff0c;還是發現…

昆蟲繁殖_“專為昆蟲而生” –好奇!

昆蟲繁殖重點 (Top highlight)The industry is changing towards a more agile approach and jacks of one trade can go extinct sooner than we think.該 行業正在發生變化 朝著更加靈活的方法和一個貿易的插Kong可以去滅絕快于我們的想法。 I’ve read a quote in a book r…

ECMAScript 2022 正式發布,有哪些新特性?

大家好&#xff0c;我是若川。持續組織了近一年的源碼共讀活動&#xff0c;感興趣的可以 點此加我微信ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系列。…

字母框如何影響UI內容的理解

What is your earliest memory of reading? Mine’s reading comics. I preferred films over books, I still do, but I seemed to have a fascination for comics. The experience of reading a comic, to me, was somewhere between watching a film and reading a novel, …