0%

async-validator源码解析(三):validator

上篇 async-validator 源码解析(二):ruleasync-validator 校验库的 rule 目录下的代码进行了分析,下面继续来填坑分析 validator 目录下的源码,自底向上理解表单校验的原理。可以从仓库 https://github.com/MageeLin/async-validator-source-code-analysisanalysis分支看到本篇中的每个文件的代码分析。

已完成:

  1. async-validator 源码解析(一):文档翻译
  2. async-validator 源码解析(二):rule
  3. async-validator 源码解析(三):validator
  4. async-validator 源码解析(四):Schema 类
  5. async-validator 源码解析(五):校验方法 validate

依赖关系

代码依赖关系如下所示:

依赖关系图

按照从底向上的方式,本篇主要分析 validator 目录。

validator

validator 和之前的 rule 关系非常密切,rule 目录下方法的主要功能是通过校验 valuerule ,来给 errors 数组添加新的 error。而 validator 则是将 value 分成各种类型,然后对不同类型的 value 执行不同的 rule 校验组合,便于回调函数 callback 对最终的 errors 数组做进一步的处理。

  1. 该目录下的校验方法的结构基本类似,但是汇总的说,第一步是判断是否需要进行校验:
  • 该字段是必需的。
  • 该字段不是必需的,但是 source 对象中该字段有值且不为空值。
  1. 如果需要校验,第二步校验时是如下的步骤:
  • 先校验该字段不为空的 rule
  • 再校验该类型值对应的其他 rule
  1. 最后就执行 callback(errors),用回调函数调用 errors,使用回调函数来进行之后的步骤。

如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 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 个参数的确也是 sourceoptions),与 validator 目录中的各类型校验方法是一样的入参。而且校验成功时同样是什么都不返回或者 callback(),校验失败时同样是返回 callback(errors)。所以在手写 validator 时就是在手写这个这个模板,rule 中的那些自定义参数也就是 validator 的类似语法糖简写。

string.js

下面以 string.js 为例,说明上述模板是如何用的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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.jsvalidator 目录的统一出口管理,有一个比较有意思的地方是 urlhexemail 这三种类型的校验其实本质上都是 type校验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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

校验任意类型只需要一步,校验不为空即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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

校验数组需要三步。第一步校验非空数组,第二步校验类型,第三步校验范围。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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

校验布尔值需要两步。第一步校验不为空值,第二步校验类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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

校验时间需要三步。第一步校验不为空,第二步校验类型,第三步校验范围。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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

校验枚举值需要两步,第一步校验不为空,第二步校验枚举。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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

校验浮点数需要三步。第一步校验不为空,第二步校验类型,第三步校验范围。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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

校验整数需要三步。第一步校验不为空,第二步校验类型,第三步校验范围。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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

校验浮点数需要两步。第一步校验不为空,第二步校验类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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

校验对象需要两步。第一步校验不为空,第二步校验类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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

校验正则表达式需要两步。第一步校验不为空,第二步校验类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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

校验整数只需要一步,校验不为空。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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

校验数字需要三步。第一步校验不为空,第二步校验类型,第三步校验范围。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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

校验整数需要两步。第一步校验不为空,第二步校验类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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;

总结一下:

  • 校验一步 —— 不为空requiredany
  • 校验两步 —— 不为空 -> 类型typeregexppatternobjectmethodbooleanenum 的第二步是校验枚举。
  • 校验三步 —— 不为空 -> 类型 -> 范围numberintegerfloatdatearray
👆 全文结束,棒槌时间到 👇