共计 8100 个字符,预计需要花费 21 分钟才能阅读完成。
背景
在应用 ivew.design 的时候,在源码中发现 form 表单的验证是应用Async Validator,而后就去看一下源码,理解原理并做一下整顿。
const validator = new AsyncValidator(descriptor); | |
let model = {}; | |
model[this.prop] = this.fieldValue; | |
validator.validate(model, { firstFields: true}, errors => { | |
this.validateState = !errors ? 'success' : 'error'; | |
this.validateMessage = errors ? errors[0].message : ''; | |
callback(this.validateMessage); | |
this.FormInstance && this.FormInstance.$emit('on-validate', this.prop, !errors, this.validateMessage || null); | |
}); |
这个是源码的版本
先理解咋用
- gitHub 源码地址
star 还是蛮多的,很多库都用了这个,只能说 666~
-
装置
npm i async-validator
- 用法
更多的细节就从源码外面去阐明了,说多了也记不得~
总体解析
- 大抵设计如下
- 最初导出的 Schema 构造函数
由下面能够看出,这个 js 库最初导出 Schema 构造函数,并且分成几个大块,原型链下面的办法,register,warning,messages,validators
warning 办法
在实例化 Schema 之前设置 warning 办法。该办法实际上是 util.js 文件外面的一个工具办法
如果放弃所有的管制太正告,你能够本人初始化 | |
Schema.warning = () => {} |
// 默认是一个空函数,这样 if 条件没有通过时将不会在控制台打印正告 | |
export let warning = () => {}; | |
// 在生产环境或者 node runtime 时,不打印正告信息 | |
if ( | |
typeof process !== 'undefined' && | |
process.env && | |
process.env.NODE_ENV !== 'production' && | |
typeof window !== 'undefined' && | |
typeof document !== 'undefined' | |
) {warning = (type, errors) => {if (typeof console !== 'undefined' && console.warn) {if (errors.every(e => typeof e === 'string')) { | |
// 实际上就是一个 console.warn | |
console.warn(type, errors); | |
} | |
} | |
}; | |
} |
messages 办法
在 Schema
中的 message 办法实际上是 message.js 文件中导出的实例,是依据不同类型的失败校验的提醒用的音讯模板.
//index.js | |
// 构造函数 | |
function Schema(descriptor) { | |
//descriptor 申明了校验规定 | |
this.rules = null; | |
// 应用公有属性_messages 保留 defaultMessages | |
this._messages = defaultMessages; | |
this.define(descriptor); | |
} | |
Schema.messages = defaultMessages; | |
//message.js | |
export function newMessages() { | |
return { | |
default: 'Validation error on field %s', | |
required: '%s is required', | |
enum: '%s must be one of %s', | |
whitespace: '%s cannot be empty', | |
date: { | |
format: '%s date %s is invalid for format %s', | |
parse: '%s date could not be parsed, %s is invalid', | |
invalid: '%s date %s is invalid', | |
}, | |
types: { | |
string: '%s is not a %s', | |
method: '%s is not a %s (function)', | |
array: '%s is not an %s', | |
object: '%s is not an %s', | |
number: '%s is not a %s', | |
date: '%s is not a %s', | |
boolean: '%s is not a %s', | |
integer: '%s is not an %s', | |
float: '%s is not a %s', | |
regexp: '%s is not a valid %s', | |
email: '%s is not a valid %s', | |
url: '%s is not a valid %s', | |
hex: '%s is not a valid %s', | |
}, | |
string: { | |
len: '%s must be exactly %s characters', | |
min: '%s must be at least %s characters', | |
max: '%s cannot be longer than %s characters', | |
range: '%s must be between %s and %s characters', | |
}, | |
number: { | |
len: '%s must equal %s', | |
min: '%s cannot be less than %s', | |
max: '%s cannot be greater than %s', | |
range: '%s must be between %s and %s', | |
}, | |
array: { | |
len: '%s must be exactly %s in length', | |
min: '%s cannot be less than %s in length', | |
max: '%s cannot be greater than %s in length', | |
range: '%s must be between %s and %s in length', | |
}, | |
pattern: {mismatch: '%s value %s does not match pattern %s',}, | |
clone() { | |
// 深拷贝 | |
const cloned = JSON.parse(JSON.stringify(this)); | |
cloned.clone = this.clone; | |
return cloned; | |
}, | |
}; | |
} | |
export const messages = newMessages(); |
当然有必要的话,这个 messages 的模板本人也是能够革新的。
//index.js | |
//Schema.prototype 的原型办法中有 message 的办法 | |
messages(messages) {if (messages) { | |
// 将 _messages 和参数 深度合并合并 | |
this._messages = deepMerge(newMessages(), messages); | |
} | |
return this._messages; | |
} | |
//util.js | |
//deepMerge 是 util.js 中的一个一般合并对象的办法,先是遍历一遍对象,而后对下一层的对象应用构造,实际上只有 2 层的合并 | |
export function deepMerge(target, source) {if (source) {for (const s in source) {if (source.hasOwnProperty(s)) {const value = source[s]; | |
if (typeof value === 'object' && typeof target[s] === 'object') {target[s] = {...target[s], | |
...value, | |
}; | |
} else {target[s] = value; | |
} | |
} | |
} | |
} | |
return target; | |
} |
//gitHub 给到的例子 | |
import Schema from 'async-validator'; | |
const cn = {required: '%s 必填',}; | |
const descriptor = {name: { type: 'string', required: true} }; | |
const validator = new Schema(descriptor); | |
// 将 cn 与 defaultMessages 深层合并 | |
validator.messages(cn); | |
... |
validators
- 为用户提供的各种数据类型的验证办法
import validators from './validator/index'; | |
Schema.validators = validators; |
-
以对 string 类型的判断为例
- rule: 在源 descriptor 中,与要校验的字段名称绝对应的校验规定。始终为它调配一个 field 属性,其中蕴含要验证的字段的名称。
// 这个样子 | |
{[field: string]: RuleItem | RuleItem[]} | |
// 例子 | |
{name:{type: "string", required: true, message: "Name is required"}} |
- value: 源对象属性中要校验的值。
- callback: 校验实现后须要调用的 callback。传递一个 Error 实例数组以判断校验失败。如果校验是同步的,则能够间接返回 false、Error 或 Error Array
callback(errors)
- source: 传给 validate 办法的源对象
- options: 额定选项
//options 的外部属性 | |
export interface ValidateOption { | |
// 是否勾销对于有效值的外部正告 | |
suppressWarning?: boolean; | |
// 当第一个验证规定生成谬误时,进行解决 | |
first?: boolean; | |
// 当指定字段的第一个验证规定生成谬误时,进行解决字段,“true”示意所有字段。firstFields?: boolean | string[];} |
- options.messages: 蕴含校验 error message 的对象,将与 defaultMessages 进行深度合并
// 所有的验证的办法都是以 rule, value, callback, source, options 为参数 | |
function string(rule, value, callback, source, options) { | |
// 须要 callback 进来的谬误列表 | |
const errors = []; | |
// 首先验证 required 为 false 或者还未填写状态就间接返回 | |
const validate = | |
rule.required || (!rule.required && source.hasOwnProperty(rule.field)); | |
if (validate) { | |
//isEmptyValue 判断是否为空值,并且将空数组也判断为 true | |
if (isEmptyValue(value, 'string') && !rule.required) {return callback(); | |
} | |
// 应用 rules 办法判断是否必填 | |
rules.required(rule, value, source, errors, options, 'string'); | |
if (!isEmptyValue(value, 'string')) { | |
// 判断 type,range 范畴(这里有波及了 len,min,max 判断),以及提供正则表达式的判断 | |
rules.type(rule, value, source, errors, options); | |
rules.range(rule, value, source, errors, options); | |
rules.pattern(rule, value, source, errors, options); | |
if (rule.whitespace === true) { | |
// 为仅由空格组成的字符串增加额定的校验 | |
rules.whitespace(rule, value, source, errors, options); | |
} | |
} | |
} | |
callback(errors); | |
} | |
export default string; |
register
除了上述提供的 validators 的办法,这个 register 用于自定义判断
Schema.register = function register(type, validator) { | |
// 必须是函数 | |
if (typeof validator !== 'function') { | |
throw new Error('Cannot register a validator by type, validator is not a function',); | |
} | |
validators[type] = validator; | |
}; |
为 validators 验证的办法提供的 rule
/** | |
* 所有的办法的传值如下 | |
* @param rule 校验的规定 | |
* @param value source 对象中该字段的值 | |
* @param source 要校验的 source 对象 | |
* @param errors 本次校验将要去增加的 errors 数组 | |
* @param options 校验选项 | |
* @param options.messages 校验的 messages | |
*/ | |
export default { | |
required, // 属性为必填 | |
whitespace, // 校验空白字符 | |
type, // 判断 type 属性 | |
range, // 通过传入的 number 类型的 len,min,max 进行判断 | |
enum: enumRule,// 校验值是否存在在枚举值列表中 | |
pattern,// 校验是否合乎校验正则表达式 | |
}; |
type 有如下类型
const custom = [ | |
'integer', | |
'float', | |
'array', | |
'regexp', | |
'object', | |
'method', | |
'email', | |
'number', | |
'date', | |
'url', | |
'hex', | |
]; |
// 枚举验证 官网案例 | |
const descriptor = {role: { type: 'enum', enum: ['admin', 'user', 'guest'] }, | |
}; |
原型链下面的办法
- messages
// 上文有交代,用于新增本人的谬误音讯提醒模板 | |
messages(messages) {if (messages) {this._messages = deepMerge(newMessages(), messages); | |
} | |
return this._messages; | |
}, |
- define
注册校验规定 rules
- getType
通过此办法取得 rule 的 type,并会判断此 type 是否是曾经定义好的 validators 中的 type
- getValidationMethod
通过此办法取得 rule 的 validator
getValidationMethod(rule) { | |
// 定义好了 validator 间接返回 | |
if (typeof rule.validator === 'function') {return rule.validator;} | |
// 收集单个 rule 外面定义的所有 key | |
const keys = Object.keys(rule); | |
const messageIndex = keys.indexOf('message'); | |
if (messageIndex !== -1) { | |
// 删除 message 属性 | |
keys.splice(messageIndex, 1); | |
} | |
// 除了 message,就只有一个 key 且是 required,返回 validators 提供的 required 办法 | |
if (keys.length === 1 && keys[0] === 'required') {return validators.required;} | |
// 否则,最初一种状况,依据 rule 定义的 type 判断 | |
// 如果 type 是非法未定义的 type 则间接返回 false | |
return validators[this.getType(rule)] || false; | |
}, |
- validate
外围办法 | |
1. 参数: | |
source_ 须要校验的对象 | |
o 即 options 形容校验的解决选项的对象(上文说的,suppressWarning,firstFields,first)oc 即 callback 校验实现后的回调函数 | |
2. 返回值是一个 Promise 对象:then 校验通过 | |
catch({errors,fields}) 校验失败 | |
errors 是一个所有 error 数组,fiels 是一个相似 {field1:[error,error...],field2:[error,error...]} 的对象 |
不得不说 asyncMap 又是怎么内容
首先它。若 options.first 为否值,;再依据 options.firstFields 是否为真值,别离执行 asyncSerialArray、asyncParallelArray 函数。
export function asyncMap(objArr, option, func, callback) { | |
// 判断 first | |
if (option.first) { | |
// 判断 options.first 是否为真值,若为真值,调用 asyncSerialArray 解决 series 数组 | |
// 当某一规定校验失败时,即终止校验,执行 callback 回调 | |
const pending = new Promise((resolve, reject) => { | |
const next = errors => {callback(errors); | |
return errors.length | |
? reject(new AsyncValidationError(errors, convertFieldsError(errors))) | |
: resolve();}; | |
const flattenArr = flattenObjArr(objArr); | |
asyncSerialArray(flattenArr, func, next); | |
}); | |
pending.catch(e => e); | |
return pending; | |
} | |
let firstFields = option.firstFields || []; | |
if (firstFields === true) {firstFields = Object.keys(objArr); | |
} | |
const objArrKeys = Object.keys(objArr); | |
const objArrLength = objArrKeys.length; | |
let total = 0; | |
const results = []; | |
const pending = new Promise((resolve, reject) => { | |
// 构建 next 函数包装 callback,目标是将所有校验器的失败文案合并再传入 callback | |
const next = errors => {results.push.apply(results, errors); | |
total++; | |
if (total === objArrLength) {callback(results); | |
return results.length | |
? reject(new AsyncValidationError(results, convertFieldsError(results)), | |
) | |
: resolve();} | |
}; | |
if (!objArrKeys.length) {callback(results); | |
resolve();} | |
objArrKeys.forEach(key => {const arr = objArr[key]; | |
// 判断 firstFields 值,别离执行 asyncSerialArray、asyncParallelArray 函数 | |
//asyncParallelArray 函数用于实现平行校验,在某个异步校验器执行过程中,平行调用下一个校验器;//asyncSerialArray 用于实现有序校验,在异步校验器执行实现后,再启用下一个校验器。if (firstFields.indexOf(key) !== -1) {asyncSerialArray(arr, func, next); | |
} else {asyncParallelArray(arr, func, next); | |
} | |
}); | |
}); | |
pending.catch(e => e); | |
return pending; | |
} |
最初
就这样了,有新的理解,前面再编辑,批改
喜爱,帮忙点赞????
正文完
发表至: javascript
2020-10-13