当前位置: 首页 > 后端技术 > Node.js

来来来,看看AsyncValidator的源码

时间:2023-04-04 00:49:43 Node.js

背景在使用ivew.design的时候,在源码中发现AsyncValidator是用来校验表单的,于是就去看源码理解原理并组织起来。constvalidator=newAsyncValidator(descriptor);letmodel={};model[this.prop]=this.fieldValue;validator.validate(model,{firstFields:true},errors=>{this.validateState=!errors?'成功':'错误';this.validateMessage=errors?errors[0].message:'';回调(this.validateMessage);this.FormInstance&&this.FormInstance.$emit('on-validate',this.prop,!errors,this.validateMessage||null);});这是源代码版本。先来了解下gitHub源码地址的使用方法。更多关于validator使用的细节会从源码中解释,说多了记不住了~整体分析大致设计如下。从上面可以看出最终导出的Schema构造函数。一大块,原型链上面的方法,register,warning,messages,validatorwarning方法在实例化Schema之前设置warning方法。该方法实际上是util.js文件中的一个工具方法。如果放弃所有控制太warning,可以初始化Schema.warning=()=>{}//默认是一个空函数,所以当if条件没有通过时,控制台不会打印警告exportletwarning=()=>{};//在生产环境或节点运行时,不会打印警告信息&&typeofwindow!=='undefined'&&typeofdocument!=='undefined'){warning=(type,errors)=>{if(typeofconsole!=='undefined'&&console.warn){if(errors.every(e=>typeofe==='string')){//实际上是一个console.warnconsole.warn(type,errors);}}};}messages方法Schema中的message方法实际上是message.js文件中导出的实例,是根据不同类型的失败检查进行提示的消息模板//index.js//构造函数Schema(descriptor){//描述符声明验证规则this.rules=null;//使用私有属性_messages保存defaultMessagesthis._messages=defaultMessages;this.define(descriptor);}Schema.messages=defaultMessages;//message.jsexportfunctionnewMessages(){return{def错误:“字段%s上的验证错误”,要求:“需要%s”,枚举:“%s必须是%s之一”,空格:“%s不能为空”,日期:{格式:“%”s日期%s对于格式%s无效',解析:'%s日期无法解析,%s无效',无效:'%s日期%s无效',},类型:{字符串:'%s不是%s,方法:“%s不是%s(函数)”,数组:“%s不是%s”,对象:“%s不是%s”,数字:'%s不是%s,日期:“%s不是%s”,布尔值:“%s不是%s”,整数:“%s不是%s”,浮点数:“%s”不是%s',正则表达式:“%s不是有效的%s”,电子邮件:“%s不是有效的%s”,url:“%s不是有效的%s”,十六进制:“%s不是有效的%s',},string:{len:'%s必须正好是%s个字符',min:'%s必须至少是%s个字符',max:'%s不能长于%s个字符',范围:'%s必须介于%s和%s个字符之间acters',},number:{len:'%s必须等于%s',min:'%s不能小于%s',max:'%s不能大于%s',range:'%s必须在%s和%s之间',},array:{len:'%s的长度必须恰好为%s',min:'%s的长度不能小于%s',max:'%s不能长度大于%s',范围:'%s的长度必须在%s和%s之间',},模式:{不匹配:'%s值%s与模式%s不匹配',},克隆(){//深贝贝constcloned=JSON.parse(JSON.stringify(this));cloned.clone=this.clone;返回克隆;},};}exportconstmessages=newMessages();当然有必要的话,这个messages的模板自己也是可以修改的//index.js//Schema.prototype的原型方法有一个消息方法messages(messages){if(messages){//深度合并_messages和参数this._messages=deepMerge(newMessages(),messages);}returnthis._messages;}//util.js//deepMerge是util.js中常用的合并对象的方法,先遍历对象,再对下一层对象使用结构体,其实只有2种图层合并导出函数deepMerge(target,source){if(source){for(constsinsource){if(source.hasOwnProperty(s)){constvalue=source[s];if(typeofvalue==='object'&&typeoftarget[s]==='object'){target[s]={...target[s],...value,};}else{目标[s]=值;}}}}returntarget;}//gitHub给出的例子importSchemafrom'async-validator';constcn={required:'%sisrequired',};constdescriptor={name:{type:'string',required:true}};constvalidator=newSchema(descriptor);//深度合并cn和defaultMessagesvalidator.messages(cn);...validators为用户提供校验各种数据类型的方法导入验证器from'./validator/index';Schema.validators=validators;以字符串类型的判断为例规则:在源描述符中,待验证的字段名对应的验证规则总是为其分配一个字段属性,其中包含待验证的字段名称。//这看起来像{[field:string]:RuleItem|RuleItem[]}//Example{name:{type:"string",required:true,message:"Nameisrequired"}}value:在源对象属性中需要勾选的值。callback:验证完成后需要调用的回调。传递错误实例数组以确定验证是否失败。如果验证是同步的,可以直接返回false、Error或ErrorArraycallback(errors)source:传给validate方法的源对象options:extraoptions//options的内部属性exportinterfaceValidateOption{//是否取消无效值内部警告suppressWarning?:boolean;//当第一个验证规则先产生错误时停止处理该字段?:boolean;//当指定字段的第一个验证规则产生错误时停止处理该字段,“true”表示所有字段。firstFields?:布尔值|string[];}options.messages:包含验证错误信息的对象,将与defaultMessages深度融合//所有验证方法都以rule,value,callback,source,options为参数functionstring(rule,value,callback,source,options){//需要回调的错误列表consterrors=[];//先验证required是否为false,如果状态未填则直接返回constvalidate=rule.required||(!rule.required&&source.hasOwnProperty(rule.field));if(validate){//isEmptyValue判断是否为空值,空数组判断为真if(isEmptyValue(value,'string')&&!rule.required){returncallback();}//使用rules方法判断是否需要rules.required(rule,value,source,errors,options,'string');if(!isEmptyValue(value,'string')){//判断类型,范围范围(这里涉及到len,min,max判断),并提供正则表达式判断rules.type(rule,value,source,errors,options);规则范围(规则、值、来源、错误、选项);rules.pattern(规则,值,来源,错误,选项);if(rule.whitespace===true){//为只包含空格的字符串添加空格外部验证rules.whitespace(rule,value,source,errors,options);}}}回调(错??误);}导出默认字符串;register除了上面提供的validators方法外,这个register是用来自定义判断Schema的。register=functionregister(type,validator){//必须是函数if(typeofvalidator!=='function'){thrownewError('无法按类型注册验证器,验证器不是函数',);}validators[type]=validator;};rule/**为validators提供验证方法*所有方法传值如下*@paramrule验证规则*@paramvaluesource对象中该字段的值*@paramsource要验证的源对象*@paramerrors为这次验证添加的错误数组*@paramoptions验证选项*@paramoptions.messages消息验证*/exportdefault{required,//propertyisrequiredwhitespace,//Check空白字符类型,//判断type属性范围,//通过传入数字的len,min,max判断typeenum:enumRule,//枚举值列表pattern中是否存在校验值,//校验是否它匹配正则表达式};type具有以下类型constcustom=['integer','float','array','regexp','object','method','email','number','date','url','hex',];//枚举验证的官方案例constdescriptor={role:{type:'enum',enum:['admin','user','guest']},};methodmethodontheprototypechain//上面解释过,用于添加自己的错误信息提示模板messages(messages){if(messages){this._messages=deepMerge(newMessages(),messages);}returnthis._messages;},定义注册验证规则rulesgetType通过该方法获取规则的类型,判断该类型是否为已经定义的验证器中的typegetValidationMethod。通过这个方法获取规则的validatorgetValidationMethod(rule){//validator定义好了,直接返回}//收集单个规则中定义的所有键constkeys=Object.keys(rule);constmessageIndex=keys.indexOf('消息');if(messageIndex!==-1){//删除消息属性keys.splice(messageIndex,1);}//除了message,只有一个key而且是required,返回validators提供的required方法if(keys.length===1&&keys[0]==='required'){returnvalidators.必需的;}//否则,最后一种情况,根据规则定义的类型进行判断//如果类型是非法未定义类型,则直接返回falsereturnvalidators[this.getType(规则)]||错误的;},validate核心方法1.参数:source_待验证对象ooptions描述验证处理选项的对象(suppressWarning,firstFields,上面首先提到)oc是callback验证完成后的回调函数2。返回值是一个Promise对象:然后验证通过catch({errors,fields})验证失败errors是一个所有错误的数组,fiels是一个类似{field1:[error,error...],field2:[error,error...]}对象要说asyncMapFirst的内容是什么,如果options.first为false;然后根据options.firstFields是否为true,执行asyncSerialArray、asyncParallelArray函数。exportfunctionasyncMap(objArr,option,func,callback){//先判断if(option.first){//判断options.first是否为真,如果为真,调用asyncSerialArray处理series数组//当a的时候规则校验失败,校验终止,执行回调回调constpending=newPromise((resolve,reject)=>{constnext=errors=>{callback(errors);returnerrors.length?reject(newAsyncValidationError(错误,convertFieldsError(errors))):resolve();};constflattenArr=flattenObjArr(objArr);asyncSerialArray(flattenArr,func,next);});pending.catch(e=>e);退货待定;}让firstFields=option.firstFields||[];如果(firstFields===true){firstFields=Object.keys(objArr);}constobjArrKeys=Object.keys(objArr);constobjArrLength=objArrKeys.length;让总计=0;常量结果=[];constpending=newPromise((resolve,reject)=>{//构造next函数包装回调,目的是将所有validator的失败副本合并到回调中constnext=errors=>{结果.push.apply(结果,错误);总计++;如果(总计===objArrLength){回调(结果);返回结果。长度?reject(newAsyncValidationError(results,convertFieldsError(results)),):resolve();}};如果(!objArrKeys.length){回调(结果);解决();}objArrKeys.forEach(key=>{constarr=objArr[key];//判断firstFields的值,分别执行asyncSerialArray、asyncParallelArrayfunction//asyncParallelArrayfunction用于实现并行验证,在异步验证器执行过程中,nextverifierisparallelcalled;//asyncSerialArray用于实现有序验证,在异步验证器中执行完成后,启用下一个验证器if(firstFields.indexOf(key)!==-1){asyncSerialArray(arr,func,next);}else{asyncParallelArray(arr,func,next);}});});pending.catch(e=>e);returnpending;}到最后就这样了,又有新的认识了,待会再编辑,修改点赞,帮我点个赞好吗?