上篇 async-validator 源码解析(二):rule 将 async-validator 校验库的 rule 目录下的代码进行了分析,下面继续来填坑分析 validator 目录下的源码,自底向上理解表单校验的原理。可以从仓库 github.com/MageeLin/as… 的analysis分支看到本篇中的每个文件的代码分析。
依赖关系
代码依赖关系如下所示:
按照从底向上的方式,本篇主要分析 validator 目录。
validator
validator 和之前的 rule 关系非常密切,rule 目录下方法的主要功能是通过校验 value 和 rule ,来给 errors 数组添加新的 error。而 validator 则是将 value 分成各种类型,然后对不同类型的 value 执行不同的 rule 校验组合,便于回调函数 callback 对最终的 errors 数组做进一步的处理。
- 该目录下的校验方法的结构基本类似,但是汇总的说,第一步是判断是否需要进行校验:
- 该字段是必需的。
- 该字段不是必需的,但是
source对象中该字段有值且不为空值。
- 如果需要校验,第二步校验时是如下的步骤:
- 先校验该字段不为空的
rule。 - 再校验该类型值对应的其他
rule。
- 最后就执行
callback(errors),用回调函数调用errors,使用回调函数来进行之后的步骤。
如下所示:
/**
* validator目录下方法的模板
*
* @param rule 校验规则
* @param value 该字段在source对象中的值
* @param callback 回调函数
* @param source 要校验的source对象
* @param options 校验选项
* @param options.messages 校验message
*/
// foo是各种各样不同类型
function foo(rule, value, callback, source, options) {
const errors = []; // 初始化errors数组
// 判断是否需要校验,这里分了两种情况
// 第一种是该字段是必需的
// 第二种是该字段不必需,但是source对象中该字段有值,也就是该字段被填写了。
const validate =
rule.required || (!rule.required && source.hasOwnProperty(rule.field));
// 如果需要校验时
if (validate) {
// 下面的if是对第二种情况又细分
// 如果对该类型不必需验证且该字段有值,但是值为空值,就需要单独拎出来
if (isEmptyValue(value, 'foo') && !rule.required) {
return callback(); // 就直接callback一个undefined
}
// 下面就是普通情况的处理了
rules.required(rule, value, source, errors, options); // 正式校验之前先把字段必需的required规则校验
// 当值不为undefined时,再进行对应的各种不同类型的规则校验
if (value !== undefined) {
rules.foo(rule, value, source, errors, options);
}
}
// 最后用回调函数调用errors,这也是validator目录下文件的最终目的
callback(errors);
}
其实回想一下,在 Element-UI 中,我们为字段创建自定义的 validator 函数,参数也是 validator(rule, value, callback) {...}(专门测试了下第 4 和第 5 个参数的确也是 source 和 options),与 validator 目录中的各类型校验方法是一样的入参。而且校验成功时同样是什么都不返回或者 callback(),校验失败时同样是返回 callback(errors)。所以在手写 validator 时就是在手写这个这个模板,rule 中的那些自定义参数也就是 validator 的类似语法糖简写。
string.js
下面以 string.js 为例,说明上述模板是如何用的:
import rules from '../rule/index.js';
import { isEmptyValue } from '../util';
/**
* 执行字符串类型校验
*
* @param rule 校验规则
* @param value 该字段在source对象中的值
* @param callback 回调函数
* @param source 要校验的source对象
* @param options 校验选项
* @param options.messages 校验message
*/
function string(rule, value, callback, source, options) {
const errors = [];
const validate =
rule.required || (!rule.required && source.hasOwnProperty(rule.field));
if (validate) {
// 注意这里是专门检验的string空值
if (isEmptyValue(value, 'string') && !rule.required) {
return callback();
}
rules.required(rule, value, source, errors, options, 'string');
if (!isEmptyValue(value, 'string')) {
// 先校验类型规则 rule.type
rules.type(rule, value, source, errors, options);
// 再校验范围规则 rule.len max min
rules.range(rule, value, source, errors, options);
// 再校验模式规则 rule.pattern
rules.pattern(rule, value, source, errors, options);
if (rule.whitespace === true) {
// 当rule.whitespace为true时,还要校验whitespace规则
rules.whitespace(rule, value, source, errors, options);
}
}
}
// 调用回调函数
callback(errors);
}
export default string;
index.js
index.js 是 validator 目录的统一出口管理,有一个比较有意思的地方是 url、hex 和 email 这三种类型的校验其实本质上都是 type校验。
import string from './string';
import method from './method';
import number from './number';
import boolean from './boolean';
import regexp from './regexp';
import integer from './integer';
import float from './float';
import array from './array';
import object from './object';
import enumValidator from './enum';
import pattern from './pattern';
import date from './date';
import required from './required';
import type from './type';
import any from './any';
/**
* 校验方法的统一出口管理
*
* @param rule 校验规则
* @param value 该字段在source对象中的值
* @param callback 回调函数
* @param source 要校验的source对象
* @param options 校验选项
* @param options.messages 校验message
*/
export default {
string,
method,
number,
boolean,
regexp,
integer,
float,
array,
object,
enum: enumValidator,
pattern,
date,
url: type, // 可以发现url hex 和 email三种类型都交给typeValidator来处理了
hex: type,
email: type,
required,
any,
};
any.js
校验任意类型只需要一步,校验不为空即可。
import rules from '../rule/index.js';
import { isEmptyValue } from '../util';
/**
* 校验任意类型
*
* @param rule 校验规则
* @param value 该字段在source对象中的值
* @param callback 回调函数
* @param source 要校验的source对象
* @param options 校验选项
* @param options.messages 校验message
*/
function any(rule, value, callback, source, options) {
const errors = [];
const validate =
rule.required || (!rule.required && source.hasOwnProperty(rule.field));
if (validate) {
if (isEmptyValue(value) && !rule.required) {
return callback();
}
// 任意类型,所以只用校验该值存在即可
rules.required(rule, value, source, errors, options);
}
callback(errors);
}
export default any;
array.js
校验数组需要三步。第一步校验非空数组,第二步校验类型,第三步校验范围。
import rules from '../rule/index';
import { isEmptyValue } from '../util';
/**
* 校验数组
*
* @param rule 校验规则
* @param value 该字段在source对象中的值
* @param callback 回调函数
* @param source 要校验的source对象
* @param options 校验选项
* @param options.messages 校验message
*/
function array(rule, value, callback, source, options) {
const errors = [];
const validate =
rule.required || (!rule.required && source.hasOwnProperty(rule.field));
if (validate) {
if (isEmptyValue(value, 'array') && !rule.required) {
return callback();
}
rules.required(rule, value, source, errors, options, 'array');
// 当值为非空数组时
if (!isEmptyValue(value, 'array')) {
// 先校验类型 rule.type
rules.type(rule, value, source, errors, options);
// 再校验范围 rule.len max min
rules.range(rule, value, source, errors, options);
}
}
callback(errors);
}
export default array;
boolean.js
校验布尔值需要两步。第一步校验不为空值,第二步校验类型。
import { isEmptyValue } from '../util';
import rules from '../rule/index.js';
/**
* 校验布尔值
*
* @param rule 校验规则
* @param value 该字段在source对象中的值
* @param callback 回调函数
* @param source 要校验的source对象
* @param options 校验选项
* @param options.messages 校验message
*/
function boolean(rule, value, callback, source, options) {
const errors = [];
const validate =
rule.required || (!rule.required && source.hasOwnProperty(rule.field));
if (validate) {
if (isEmptyValue(value) && !rule.required) {
return callback();
}
rules.required(rule, value, source, errors, options);
if (value !== undefined) {
// 校验类型
rules.type(rule, value, source, errors, options);
}
}
callback(errors);
}
export default boolean;
date.js
校验时间需要三步。第一步校验不为空,第二步校验类型,第三步校验范围。
import rules from '../rule/index.js';
import { isEmptyValue } from '../util';
/**
* 校验时间
*
* @param rule 校验规则
* @param value 该字段在source对象中的值
* @param callback 回调函数
* @param source 要校验的source对象
* @param options 校验选项
* @param options.messages 校验message
*/
function date(rule, value, callback, source, options) {
// console.log('integer rule called %j', rule);
const errors = [];
const validate =
rule.required || (!rule.required && source.hasOwnProperty(rule.field));
// console.log('validate on %s value', value);
if (validate) {
if (isEmptyValue(value, 'date') && !rule.required) {
return callback();
}
rules.required(rule, value, source, errors, options);
// 当是非空 时间值时
if (!isEmptyValue(value, 'date')) {
let dateObject;
// 不管是时间字符串还是时间实例,都格式化成Date的实例
if (value instanceof Date) {
dateObject = value;
} else {
dateObject = new Date(value);
}
rules.type(rule, dateObject, source, errors, options);
if (dateObject) {
// 校验时间戳是否在范围内 rule.len max min
rules.range(rule, dateObject.getTime(), source, errors, options);
}
}
}
callback(errors);
}
export default date;
enum.js
校验枚举值需要两步,第一步校验不为空,第二步校验枚举。
import rules from '../rule/index.js';
import { isEmptyValue } from '../util';
const ENUM = 'enum';
/**
* 校验枚举值列表
*
* @param rule 校验规则
* @param value 该字段在source对象中的值
* @param callback 回调函数
* @param source 要校验的source对象
* @param options 校验选项
* @param options.messages 校验message
*/
function enumerable(rule, value, callback, source, options) {
const errors = [];
const validate =
rule.required || (!rule.required && source.hasOwnProperty(rule.field));
if (validate) {
if (isEmptyValue(value) && !rule.required) {
return callback();
}
rules.required(rule, value, source, errors, options);
if (value !== undefined) {
// 校验枚举值规则
rules[ENUM](rule, value, source, errors, options);
}
}
callback(errors);
}
export default enumerable;
float.js
校验浮点数需要三步。第一步校验不为空,第二步校验类型,第三步校验范围。
import rules from '../rule/index.js';
import { isEmptyValue } from '../util';
/**
* 校验浮点数
*
* @param rule 校验规则
* @param value 该字段在source对象中的值
* @param callback 回调函数
* @param source 要校验的source对象
* @param options 校验选项
* @param options.messages 校验message
*/
function floatFn(rule, value, callback, source, options) {
const errors = [];
const validate =
rule.required || (!rule.required && source.hasOwnProperty(rule.field));
if (validate) {
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);
}
export default floatFn;
integer.js
校验整数需要三步。第一步校验不为空,第二步校验类型,第三步校验范围。
import rules from '../rule/index.js';
import { isEmptyValue } from '../util';
/**
* 校验一个数字是否为整数
*
* @param rule 校验规则
* @param value 该字段在source对象中的值
* @param callback 回调函数
* @param source 要校验的source对象
* @param options 校验选项
* @param options.messages 校验message
*/
function integer(rule, value, callback, source, options) {
const errors = [];
const validate =
rule.required || (!rule.required && source.hasOwnProperty(rule.field));
if (validate) {
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);
}
export default integer;
method.js
校验浮点数需要两步。第一步校验不为空,第二步校验类型。
import rules from '../rule/index.js';
import { isEmptyValue } from '../util';
/**
* 校验函数
*
* @param rule 校验规则
* @param value 该字段在source对象中的值
* @param callback 回调函数
* @param source 要校验的source对象
* @param options 校验选项
* @param options.messages 校验message
*/
function method(rule, value, callback, source, options) {
const errors = [];
const validate =
rule.required || (!rule.required && source.hasOwnProperty(rule.field));
if (validate) {
if (isEmptyValue(value) && !rule.required) {
return callback();
}
rules.required(rule, value, source, errors, options);
if (value !== undefined) {
// 只校验类型规则
rules.type(rule, value, source, errors, options);
}
}
callback(errors);
}
export default method;
object.js
校验对象需要两步。第一步校验不为空,第二步校验类型。
import rules from '../rule/index.js';
import { isEmptyValue } from '../util';
/**
* 校验对象
*
* @param rule 校验规则
* @param value 该字段在source对象中的值
* @param callback 回调函数
* @param source 要校验的source对象
* @param options 校验选项
* @param options.messages 校验message
*/
function object(rule, value, callback, source, options) {
const errors = [];
const validate =
rule.required || (!rule.required && source.hasOwnProperty(rule.field));
if (validate) {
if (isEmptyValue(value) && !rule.required) {
return callback();
}
rules.required(rule, value, source, errors, options);
if (value !== undefined) {
// 校验类型规则
rules.type(rule, value, source, errors, options);
}
}
callback(errors);
}
export default object;
pattern.js
校验 pattern 需要两步。第一步校验不为空,第二步校验 pattern。
import rules from '../rule/index.js';
import { isEmptyValue } from '../util';
/**
* 校验pattern
*
*
* 当rule中只包含pattern而不规定type为string时,执行这个校验
*
* @param rule 校验规则
* @param value 该字段在source对象中的值
* @param callback 回调函数
* @param source 要校验的source对象
* @param options 校验选项
* @param options.messages 校验message
*/
function pattern(rule, value, callback, source, options) {
const errors = [];
const validate =
rule.required || (!rule.required && source.hasOwnProperty(rule.field));
if (validate) {
if (isEmptyValue(value, 'string') && !rule.required) {
return callback();
}
rules.required(rule, value, source, errors, options);
if (!isEmptyValue(value, 'string')) {
// 校验正则表达式规则
rules.pattern(rule, value, source, errors, options);
}
}
callback(errors);
}
export default pattern;
regexp.js
校验正则表达式需要两步。第一步校验不为空,第二步校验类型。
import rules from '../rule/index.js';
import { isEmptyValue } from '../util';
/**
* 校验正则表达式本身
*
* @param rule 校验规则
* @param value 该字段在source对象中的值
* @param callback 回调函数
* @param source 要校验的source对象
* @param options 校验选项
* @param options.messages 校验message
*/
function regexp(rule, value, callback, source, options) {
const errors = [];
const validate =
rule.required || (!rule.required && source.hasOwnProperty(rule.field));
if (validate) {
if (isEmptyValue(value) && !rule.required) {
return callback();
}
rules.required(rule, value, source, errors, options);
if (!isEmptyValue(value)) {
// 校验类型规则
rules.type(rule, value, source, errors, options);
}
}
callback(errors);
}
export default regexp;
required.js
校验整数只需要一步,校验不为空。
import rules from '../rule/index.js';
/**
* 校验必需字段
*
* @param rule 校验规则
* @param value 该字段在source对象中的值
* @param callback 回调函数
* @param source 要校验的source对象
* @param options 校验选项
* @param options.messages 校验message
*/
function required(rule, value, callback, source, options) {
// 初始化errors数组
const errors = [];
// 如果是数组,type就给‘array’,不是数组就typeof来求
// typeof的结果:undefined object boolean number bigint string symbol function
const type = Array.isArray(value) ? 'array' : typeof value;
// 这里去调用rules.required来给errors数组添加error
rules.required(rule, value, source, errors, options, type);
// 用回调函数来处理errors数组
callback(errors);
}
export default required;
number.js
校验数字需要三步。第一步校验不为空,第二步校验类型,第三步校验范围。
import rules from '../rule/index.js';
import { isEmptyValue } from '../util';
/**
* 校验数字
*
* @param rule 校验规则
* @param value 该字段在source对象中的值
* @param callback 回调函数
* @param source 要校验的source对象
* @param options 校验选项
* @param options.messages 校验message
*/
function number(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) {
// 先校验类型规则 rule.type
rules.type(rule, value, source, errors, options);
// 再校验范围规则 rule.len min max
rules.range(rule, value, source, errors, options);
}
}
callback(errors);
}
export default number;
type.js
校验整数需要两步。第一步校验不为空,第二步校验类型。
import rules from '../rule/index.js';
import { isEmptyValue } from '../util';
/**
* 校验类型
*
* @param rule 校验规则
* @param value 该字段在source对象中的值
* @param callback 回调函数
* @param source 要校验的source对象
* @param options 校验选项
* @param options.messages 校验message
*/
function type(rule, value, callback, source, options) {
const ruleType = rule.type;
const errors = [];
const validate =
rule.required || (!rule.required && source.hasOwnProperty(rule.field));
if (validate) {
if (isEmptyValue(value, ruleType) && !rule.required) {
return callback();
}
rules.required(rule, value, source, errors, options, ruleType);
if (!isEmptyValue(value, ruleType)) {
// 校验type规则 rule.type
rules.type(rule, value, source, errors, options);
}
}
callback(errors);
}
export default type;
总结一下:
- 校验一步 ——
不为空:required、any。 - 校验两步 ——
不为空 -> 类型:type、regexp、pattern、object、method、boolean。enum的第二步是校验枚举。 - 校验三步 ——
不为空 -> 类型 -> 范围:number、integer、float、date、array。