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

這篇文章詳細分析一下 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
對象, firstFields
為 true
,表示同一個字段如果有多個校驗規則,一旦出現校驗不通過的規則后邊的規則就不執行了。
還可以設置 first
為 true
,這個是針對整個校驗對象的,如果某個字段校驗不通過,那么后邊所有的字段就不再校驗了。
第三個參數是校驗結束后的回調函數,erros
保存了所有校驗失敗的字段以及 message
信息。
因此,上邊代碼的輸出如下:

list
對應結果的 message
是默認為我們添加的,limit
對應結果的 message
是我們自己設置的,會覆蓋默認的 message
。
因為我們設置了 firstFields
為 true
,所以只校驗了 limit
的第一個規則,第二個規則就沒有走到。
我們給 limit
設置一個值,讓它走到第二個校驗規則。
validator.validate({?list:?'12',?limit:?3?},{?firstFields:?true?},(errors,?fields)?=>?{if?(errors)?{console.log('錯誤列表',?errors);}},
);
輸出如下:

此時 limit
對應結果就是一個 Error
對象了,Error
對象除了本身的 message
屬性,默認還為我們添加了 field
和 filedValue
屬性。
預處理 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',},],
};
主要做了三件事情:
把每個字段的校驗規則統一成了一個數組對象
把原本的校驗對象放到了
rule
屬性中,并且添加了value
、source
、field
屬性根據
required
和type
補充了默認的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;
}
所有的校驗函數都是提前定義好的:

在 前端的設計模式中-策略模式 中我們也提到過上邊的邏輯。
循環校驗
當我們有了預處理好的所有字段的校驗規則。
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);}
}
rule
、data.value
、data.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
會返回一個函數,將錯誤列表進行填充,主要就是補充了 field
和 fieldValue
屬性。
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'));
。
那么處理前是下圖:

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

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

同樣的,處理后也是塞入 field
和 fieldValue
屬性。

再回到我們的雙重循環中。
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
向上邊我們直接粗暴的寫雙重循環去依次校驗也沒有問題,但因為校驗庫還支持一些參數,比如前邊介紹的:

如果是 for
循環中去處理 firstFields
和 first
的邏輯,就過于耦合了,未來再擴充其他邏輯,雙重循環中的邏輯就會越來越復雜。
async-validator
?的處理方式在這里就比較優雅了,實現了 asyncMap
方法,作用就是遍歷 series
數組,并且處理了 firstFields
和 first
參數的邏輯。
下邊來分析一下實現:
看一下 asyncMap
的入口參數。
export?function?asyncMap(objArr:?Record<string,?RuleValuePackage[]>,option:?ValidateOption,func:?ValidateFunc,callback:?(errors:?ValidateError[])?=>?void,source:?Values,
){}
接受 5
個參數:
objArr
:要遍歷的 rule
規則,就是我們前邊生成的 series
數組,即雙重循環遍歷的對象。
option
:最開始傳入的 option
,可能包含 firstFields
和 first
屬性。
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
的值去調用 asyncSerialArray
和 asyncParallelArray
。內存循環判斷結束后會調用上邊的 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
,所以兩個校驗都沒通過,應該輸出下邊的內容:

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

原因就在于第一個 validator
進行了兩次 cb
,然后內層循環的 callback
就提前調用了。
validator(r,?v,?cb)?{if?(v?<?100)?{cb('校驗1');}cb();
},
因此我們最好保證一個 validator
只進行一次 cb
,走到 cb
后就直接 return
。(因為 Element
會設置 firstFields
為 true
,所以其實有多個 cb
也不影響最終結果)
validator(r,?v,?cb)?{if?(v?<?100)?{return?cb('校驗1');}cb();
},
并且一定要有一個 cb
,不然最終的回調函數永遠也不會執行了,這就是為什么 Element
提示我們要進行 cb
。

但這里說的也不夠嚴謹,我們也可以返回字符串,或者字符串數組、布爾值等, 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
?開源庫的核心源碼了,希望對你有幫助。
·················?若川簡介?·················
你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》20余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經堅持寫了8年,點擊查看年度總結。
同時,最近組織了源碼共讀活動,幫助4000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。
掃碼加我微信 ruochuan12、拉你進源碼共讀群
今日話題
目前建有江西|湖南|湖北?籍 前端群,想進群的可以加我微信 ruochuan12?進群。分享、收藏、點贊、在看我的文章就是對我最大的支持~