阿里出品的 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(){};