阿里出品的 antd 和 ElementUI 组件库中表单校验默认使用的 async-validator,它在 gitbub 上也获得了 3.8k 的 star,可见这个库十分强大,奈何只有英文文档看的蛋疼,因此花点时间翻译一下以便日后查看和为新手同事提供文档,原文都以折叠的方式保留着,看不懂我的描述可以展开看看原文。
结合 github 上的例子能方便理解
(大部分原因是我英文水平不够,但是明明是中国人写的为啥不顺手写个中文的 readme 呢,虽然就算翻译成了中文也还是晦涩难懂。。。)
正文开始。
async-validator
一个用于表单异步校验的库,参考了 https://github.com/freeformsystems/async-validate
API
下述内容来自于 async-validate. 的早期版本
Usage 使用方法
基本的使用方法:定义一个 descriptor,将它传入 schema,得到一个 validator。将需要校验的对象和回调传入 validator.validate 方法中。
注:descriptor 是对校验规则的描述,validator 是根据校验规则得到的校验器
var schema = require('async-validator'); var descriptor = { name: { type: "string", required: true, validator: (rule, value) => value === 'muji', }, }; var validator = new schema(descriptor); validator.validate({name: "muji"}, (errors, fields) => { if(errors) { // validation failed, errors is an array of all errors // fields is an object keyed by field name with an array of // errors per field // 校验未通过的情况,errors 是所有错误的数组 // fields 是一个 object,以字段作为 key 值,该字段对应的错误数组作为 value // (其实 fields 就是把 errors 按照原对象的 key 值分组) return handleErrors(errors, fields); } // validation passed // 这里说明校验已通过 }); // PROMISE USAGE // Promise 式用法 validator.validate({ name: "muji", asyncValidator: (rule, value) => axios.post('/nameValidator', { name: value }), }, (errors, fields) => { if(errors) { // validation failed, errors is an array of all errors // fields is an object keyed by field name with an array of // errors per field // 校验未通过的情况,errors 和 fields 同上 return handleErrors(errors, fields); } // validation passed // 校验通过 }) .then(() => { // validation passed // 校验通过 }) .catch(({ errors, fields }) => { return handleErrors(errors, fields); })
Validate 方法参数
function(source, [options], callback): Promise
source
: 需要校验的对象(必填).options
: 校验选项(可选).callback
: 校验完成时的回调(必填).
方法返回一个 Promise 对象:
then()
,说明校验通过catch({ errors, fields })
,校验未通过,errors, fields 含义见前面示例
Options 选项
-
first
: Boolean, 遇见第一个未通过校验的值时便调用callback
回调,不再继续校验剩余规则。
适用情况:校验涉及到多个异步调用,比如数据库查询,而你只需要获取首个校验错误时 -
firstFields
: Boolean|String[], 对于指定字段,遇见第一条未通过的校验规则时便调用callback
回调,而不再校验该字段的其他规则 ,传入true
代表所有字段。
Rules
Rules 也可以是用于校验的函数
function(rule, value, callback, source, options)
rule
: 当前校验字段在 descriptor 中所对应的校验规则,其中的 field 属性是当前正在校验字段的名称value
: 当前校验字段的值callback
: 在校验完成时的回调,传入Error
[或者是一个数组] 代表校验失败,如果校验是同步的话,直接返回false
或Error
或Error
数组也可以(注:异步校验通过时直接不带参数调用callback()
,代表没有错误)source
: 传入validate
方法的 object,也就是需要校验的对象options
: 传入的额外选项options.messages
: 对象包含的校验错误提示信息,会被合并到默认的提示信息中
传入 validate
或 asyncValidate
的 options 被带到了校验函数中,以便你可以在校验函数中拿到数据(比如 model 引用)。然而,option中部分属性名是被保留的,你如果使用了的话会被覆盖掉,其中包括 messages
, exception
和 error
。
var schema = require('async-validator'); var descriptor = { name(rule, value, callback, source, options) { var errors = []; if(!/^[a-z0-9]+$/.test(value)) { errors.push( new Error( util.format("%s must be lowercase alphanumeric characters", rule.field))); } return errors; } } var validator = new schema(descriptor); validator.validate({name: "Firstname"}, (errors, fields) => { if(errors) { return handleErrors(errors, fields); } // validation passed });
在需要对一个字段设置多条校验规则时,可以把规则设为一个数组,比如
var descriptor = { email: [ {type: "string", required: true, pattern: schema.pattern.email}, {validator(rule, value, callback, source, options) { var errors = []; // test if email address already exists in a database // and add a validation error to the errors array if it does return errors; }} ] }
Type 内置类型
下列是 type
可用的值:
string
: 必须是string
.This is the default type.
number
: 必须是number
.boolean
: 必须是boolean
.method
: 必须是function
.regexp
: 必须是正则或者是在调用new RegExp
时不报错的字符串.integer
: 整数.float
: 浮点数.array
: 必须是数组,通过Array.isArray
判断.object
: 是对象且不为数组.enum
: 值必须出现在enmu
枚举值中.date
: 合法的日期,使用Date
判断url
: url.hex
: 16进制.email
: 邮箱地址.
Required
required
属性代表这个字段必须出现在对象中
Pattern
pattern
属性代表需要符合的正则
Range
使用 min
和 max
属性定义范围,对于字符串和数组会与 value.length
比较,对于数字会直接与值比较
Length
使用 len
属性直接指定长度,会与字符串和数组的 value.length
比较相等,对于数字会直接与值比较是否相等
如果 len
与 min
和 max
同时使用, len
优先。
Enumerable
可枚举值
对于可以枚举出所有情况的类型,可以使用枚举校验,如下:
var descriptor = { role: {type: "enum", enum: ['admin', 'user', 'guest']} }
Whitespace
把仅包含空格的字段视为错误是很典型的做法,为了额外测试字段是否只有空格,添加 whitespace
属性并设为true。这个属性要求字段必须为 string
类型。
如果你想要修正用户的输入而不是测试有无空格,查看 transform 中去除空格的例子。
Deep Rules 深层规则
如果需要校验一个深层的对象,你需要使用 fields
属性来设置嵌套的规则
var 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} } var validator = new schema(descriptor); validator.validate({ address: {} }, (errors, fields) => { // errors for address.street, address.city, address.zip });
需要注意的是,如果没有在父规则上指定 required
属性,在校验对象中不存在这个属性是完全合法的,嵌套的深层规则也不会运行。
深层规则提供了直接一个定义嵌套规则的方式,让你可以简化传递给 schema.validate()
的 options
。
var descriptor = { address: { type: "object", required: true, options: {single: true, first: 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} } var validator = new schema(descriptor); validator.validate({ address: {} }) .catch(({ errors, fields }) => { // now only errors for street and name });
如果你像下面这样写,父规则也会被校验
var descriptor = { roles: { type: "array", required: true, len: 3, fields: { 0: {type: "string", required: true}, 1: {type: "string", required: true}, 2: {type: "string", required: true} } } }
比如用于 {roles: ["admin", "user"]}
会产生两个错误,一个是数组长度不匹配,一个是缺少了索引为 2
的元素
defaultField 默认字段
defaultField
属性可以在 array
和 object
类型中用于校验所有的值,它可以是一个包含有校验规则的对象或数组。 例子如下:
var descriptor = { urls: { type: "array", required: true, defaultField: {type: "url"} } }
注意,defaultField
是 fields
的扩展,见 deep rules.
Transform 变换
有时候需要在校验前修改值,强制修改为特定格式。 为此在校验规则中添加了 transform
, 这个属性会在校验前执行,以适当的方式改变原始对象的值。(也就是返回值会作用在原始对象的值上)
var schema = require('async-validator'); var sanitize = require('validator').sanitize; var descriptor = { name: { type: "string", required: true, pattern: /^[a-z]+$/, transform(value) { return sanitize(value).trim(); } } } var validator = new schema(descriptor); var source = {name: " user "}; validator.validate(source) .then(() => assert.equal(source.name, "user"));
如果没有 transform
函数校验会失败因为前后空格导致正则与输入不符,但在添加了 transform
函数后便可通过因为字段已经被清洗了(或者翻译为使输入值符合预期格式)
Messages 提示信息
在某些需求下,你可能需要格式化支持或者想要不同校验错误信息。
最简单的方式就是直接为 message
属性赋值:
{name:{type: "string", required: true, message: "Name is required"}}
消息可以是任意类型的,比如 JSX
:
{name:{type: "string", required: true, message: <b>Name is required</b>}}
也可以是函数,比如使用 vue-i18n 时:
{name:{type: "string", required: true, message: () => this.$t( 'name is required' )}}
有时候你只是需要对相同的校验规则定义不同语言的提示信息,在这种情况下为各种语言重复定义信息就显得很多余。
你也可以采取这个方案:定义你自己的提示信息并赋值给 schema
:
var schema = require('async-validator'); var cn = { required: '%s 必填', }; var descriptor = {name:{type: "string", required: true}}; var validator = new schema(descriptor); // deep merge with defaultMessages validator.messages(cn); ...
如果你要定义自己的校验函数,最好将提示信息赋值给消息对象,并在校验函数中通过 options.messages
访问消息。(说实话我没看懂是什么意思,应该是指不要把消息硬编码写在校验函数里面而是通过option传递,以便修改)
asyncValidator 异步校验函数
你可以对指定的字段自定义包含异步操作的校验函数
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 }); } } };
validator 校验函数
你也可像下面这样自定义校验函数:
const fields = { field:{ validator(rule,value,callback){ return value === 'test'; }, message: 'Value is not equal to "test".', }, field2:{ validator(rule,value,callback){ return new Error(`'${value} is not equal to "test".'`); }, }, arrField:{ validator(rule, value){ return [ new Error('Message 1'), new Error('Message 2'), ]; } }, };
FAQ
How to avoid warning 如何关闭警告
var Schema = require('async-validator'); Schema.warning = function(){};