yup 的 test 函数
题图:Photo by Sergey Norkov on Unsplash
前文 花萼横江:yup 入门 说,使用 when 或者 ref 虽然可以做到一部分复杂验证,但受限于语法本身限制,灵活性不够强。最好有一种方法可以自由定制验证规则。这就是 test 函数 。
还是以 string schema 为例:
yup.string().test('test-name', 'error message', function(value){})
最后一个参数就是 test 函数,test 函数可以访问到 schema 当前正在验证的数据,比如如果调用 validate 传入字符串 remind,这个值就是 remind。
function 还是 arrow function?
很多场景下 arrow function 比传统的 function 更好,因为 test 绑定早在定义时就确定了。如果使用 arrow function,yup 就必须将 context(包含metadata,和其他function,如订制 error的 createError ) 作为函数参数的一部分传入进来。比如 test 函数变成:
yup.string().test('test-name','error message',function(value, context){})
但是 yup 没有采用这种方式,它推荐使用传统的方式定义 test function,这样当 yup 执行 test function 时,this 就指向对应的 context,因此我们通过 this 访问 context。比如,通过 this.parent 访问 object 里其他 field 的当前数据。
yup.object({
gender: yup.string(),
age: yup.number().test('my-test', 'error message', function(value) {
const gender = this.parent.gender
})
上面顺便介绍了访问其他 field 的方法:this.parent。能够访问其他数据,已经可以实现非常复杂的逻辑订制了。
当然最后,test function 要依据逻辑的输出,来告诉 yup 验证的结果。一般情况下,使用 boolean 返回值标志验证是否成功。如果错误情况超过一个,对于每一种情况,可能需要定制不同的错误信息,那就需要使用到 createError。
this.createError
yup.object({
fieldA: yup.number(),
fieldB: yup.number().test('my-test', 'default message', function(fieldB) {
const fieldA = this.parent.fieldA
if (fieldA * fieldB > 100) {
return this.createError({message: 'error one'})
} else if (fieldA * fieldB < 50 && fieldA * fieldB >= 0) {
return this.createError({message: 'error two'})
} else if (fieldA * fieldB < 0){
return false
} else {
// no error
return true
})
createError 接受一个对象作为参数,对象包含三个属性,path,message 和 params。上面的例子只使用了 message,即最终的错误信息。只设置 message 是最常见的用法。因为 path (订制错误路径,后面会讲到用处,params 设置 context 里的可用参数)。
如果不设置 message,或者甚至不调用 createError 而是返回 false,yup 使用 default message 作为错误信息 。
default message 可以使用插值语法,调用 params 里的参数来渲染不同的错误信息。比如
yup.object({
fieldA: yup.number(),
fieldB: yup.number().test('my-test', 'error(${code}) message', function(fieldB) {
const fieldA = this.parent.fieldA
if (fieldA * fieldB > 100) {
return this.createError({params: {code: '400'}})
} else if (fieldA * fieldB < 50 && fieldA * fieldB >= 0) {
return this.createError({message: 'error two', params: {code: '400'}})
} else if (fieldA * fieldB < 0){
return false
} else {
// no error
return true
})
注意 'error(${code}) message' 使用字符串语法,而非es2015引进的模板字符串,因为后者会在编译期就去链接对应的变量,但是 code 并非是预先定义的变量,所以为报错。所以这里只是普通的字符串里使用了类似模板字符串的语法罢了。
第一和第二个错误分支都设置了 code 参数,但实际上如果第二个分支生效,返回的错误是 error two 而非默认错误。
最后是 path,一般情况下不需要修改path来订制错误路径。但也有这样的例子,比如用户先输入一遍 password 和 confirmPassword,不提交,然后修改 password,此时错误信息应该显示在 confirmPassword 上,而不是正在修改的 password上。任何时候都应该是 confirmPassword 的锅,不是吗?
yup.object({
confirmPassword: yup.string().required().test('cp-test', 'confirm password doesn\'s match', function(confirmPassword) {
const password = this.parent.password
if (password !== confirmPassword) {
return false
return true
password: yup.string().required().test('p-test', 'confirm password doesn\'s match', function(password) {
const confirmPassword = this.parent.confirmPassword