想到以往写表单,后端的模板里加上一堆东西,还有JS代码用来组织不正确的表单,还要加上HTML5里的新input标签,就算这样还需要在后端验证。虽然后端验证是避免不了的,但是学了Vue之后,发现还有很多库可以用,这个Vuelidate就是很方便的验证库。
Vuelidate的官网是https://vuelidate.netlify.com/,文档也在其中。开始吧
安装和导入
安装没有什么可说的了,npm install vuelidate --save
搞定
之后就像普通的插件一样,使用之前需要在项目里导入:
import Vue from 'vue'
import Vuelidate from 'vuelidate'
Vue.use(Vuelidate)
如果是直接在页面中使用,则直接可以导入:
<script src="vuelidate/dist/vuelidate.min.js"></script>
为字段设置验证功能
还是用学axios的库来学习Vuelidate。
给一个字段加上验证功能主要有如下步骤:
- 项目中导入Vuelidate
- 在组件中添加新属性和设置验证器
- 在绑定属性的标签上添加一个
@input
事件,通过$v
对象获取验证结果
- 根据验证结果进行其他逻辑
在signup.vue
中,是一个用于向Firebase新注册用户的表单,原来是没有任何验证的,现在要给这个表单加上验证功能。首先是email
字段。
先到main.js
中导入Vuelidate
:
import Vuelidate from 'vuelidate'
Vue.use(Vuelidate)
然后我们到signup.vue
组件中,给组件添加一个新的属性validations
,这个属性只有在使用Vuelidate的时候才生效。
export default {
data() {
return {
email: '',
age: null,
password: '',
confirmPassword: '',
country: 'usa',
hobbyInputs: [],
terms: false
}
},
validations :{
email: {
}
},
......
}
这里的validations
中的每一个属性,都表示要验证的字段名称,名称必须和data
中定义的属性名称一样,这里的email
属性名就对应data
中的email
属性。
之后就要开始针对这个字段添加验证器了,验证器需要从Vuelidate的包中导入,然后设置在字段对应的属性中,添加如下代码:
import {required, email} from 'vuelidate/lib/validators';
export default {
data() {
return {
email: '',
age: null,
password: '',
confirmPassword: '',
country: 'usa',
hobbyInputs: [],
terms: false
}
},
validations :{
email:{
required,
email
}
},
......
}
这表明给这个字段添加验证器,验证器需要从Vuelidate的包中导入,之后作为一个属性添加到email
对象上,这里使用了ES6的语法,同名绑定,实际上属性名字可以任意取。
做好关联之后,是设置动作,也就是在双向绑定email
的input
元素中添加:
<input
type="email"
id="email"
<span style="color: red">@input="$v.email.$touch()"</span>
v-model="email">
{{$v}}
监听input
事件,v-model
也监听这个事件,所以这里实际上在同样一个事件上加上了处理。
$v.email.$touch()
中的$v
就是验证对象,在后边显示出来看看其中的内容。之后的email
就是刚才设置的新属性。而$touch()
方法是一个特殊的方法,调用这个方法表示进行验证,此时$v
中的内容才会有变化。
正因为如此,$touch()
的调用时机不一定是这里,也可以是按下提交按钮之前,或者其他任何逻辑的时候。
现在来启动项目试试,看看添加了验证器之后会有什么变化:
在Email的Input框为空的时候,即刚进入页面,看到如下显示:
{
"email":{
"required": false,
"email": true,
"$model": "",
"$invalid": true,
"$dirty": false,
"$anyDirty": false,
"$error": false,
"$anyError": false,
"$pending": false,
"$params": {
"required": {
"type": "required"
},
"email":
{ "type": "email" }
}
},
"$model": null,
"$invalid": true,
"$dirty": false,
"$anyDirty": false,
"$error": false,
"$anyError": false,
"$pending": false,
"$params": { "email": null }
}
这其实是一个验证结果对象,看其中的email
的最开始两个属性,就是我们定义的required
和email
验证结果。required
要求不能不输入,所以显示false
,而email
之所以显示true
,是因为默认初始状态是true
。
到框里随便输入几个字符,但不要输入一个完整电子邮件地址,会看到其中的required
变成了true
,而email
变成了false
,实际上整个$v
对象也有变化:
{
"email": {
"required": true,
"email": false,
"$model": "cony",
"$invalid": true,
"$dirty": true,
"$anyDirty": true,
"$error": true,
"$anyError": true,
"$pending": false,
"$params": {
"required": {
"type": "required"
},
"email": {
"type": "email"
}
}
},
"$model": null,
"$invalid": true,
"$dirty": true,
"$anyDirty": true,
"$error": true,
"$anyError": true,
"$pending": false,
"$params": {
"email": null
}
}
email
属性中的几个验证结果解释如下:
$model
,实际输入的值
$invalid
,如果任何一个验证器没通过,就是true
$dirty
,表示是不是从未点击过这个控件
$anydirty
,任何一个框被点击过
$error
,在$invalid
和$dirty
都为true
的情况下为true
,表示用户点击过但仍未输入正确,这是为了防止进入页面,用户还没碰过这个框的时候就报错。
$anyError
,顾名思义,任一个$error
出错的时候为true
$pending
,表示正在验证当前的这个字段,在异步验证的时候有用。
$params
,表示当前字段设置的所有验证器的列表。
其后的属性表示整个表单的验证结果,也就是说如果设置了多个验证字段,都会在当前组件的同一个$v
对象中反映出来。
现在输入一个完整的电子邮件地址,再来看看结果:
{
"email": {
"required": true,
"email": true,
"$model": "conyli@gmail.com",
"$invalid": false,
"$dirty": true,
"$anyDirty": true,
"$error": false,
"$anyError": false,
"$pending": false,
"$params": {
"required": {
"type": "required"
},
"email": {
"type": "email"
}
}
},
"$model": null,
"$invalid": false,
"$dirty": true,
"$anyDirty": true,
"$error": false,
"$anyError": false,
"$pending": false,
"$params": {
"email": null
}
}
可以看到除了dirty
两个验证结果之外,其他都没有任何错误了。
当然,在实际项目中不能够直接打印$v
对象,而是通过检查其属性,将错误信息反映在界面上。
将验证结果反映在UI上
可以发现,只要根据$v
对象提供的各种属性的布尔值来进行判断,就可以得到当前应该提醒用户的内容,进而将其反映在UI上就可以了。
考虑如下几个场景:
- 用户在刚进入界面的时候,
required
是false
,但此时dirty
是true
,所以不应该提示用户错误信息
- 如果
dirty
是false
,required
有错,应该进行提示
- 如果
dirty
是false
,email
有错,应该进行提示
$error
表示$invalid
为true
,dirty
也是true
,所以是通用提示,表示用户输入了之后仍未正确
基于上述逻辑,可以编写代码如下:
<div class="input" :class="{invalid: $v.email.$error}">
<label for="email" :style="{color: $v.email.$error? 'red':''}">Mail</label>
<input
type="email"
id="email"
@input="$v.email.$touch()"
v-model="email">
</div>
编写样式如下:
.input.invalid input {
border: 1px solid red;
background-color: #ffc9a9;
}
之后如果录入错误,可以看到输入框的颜色和label均改变。
然后可以添加一些文字提示:
<div class="input" :class="{invalid: $v.email.$error}">
<label for="email" :style="{color: $v.email.$error? 'red':''}">Mail</label>
<input
type="email"
id="email"
@blur="$v.email.$touch()"
v-model="email">
<p v-if="!$v.email.email">请输入正确的Email地址。</p>
<p v-if="!$v.email.required && $v.email.$dirty">Email地址不能为空。</p>
</div>
红字的部分结合其中的各种验证条件,显示不同的错误信息。这里要注意的就是一开始用户还没有输入的时候,不能去提示错误,这个时候$v
对象提供的$dirty
就派上用场了。
input框失去焦点之后进行验证
在上边的例子里,只要一开始输入,监听的input方法就会开始判断是否输入错误,但实际上用户在输入的过程中被提示错误,显得不太好,这个时候可以绑定blur,让input框失去焦点的时候才判断。
<div class="input" :class="{invalid: $v.email.$error}">
<label for="email" :style="{color: $v.email.$error? 'red':''}">Mail</label>
<input
type="email"
id="email"
@blur="$v.email.$touch()"
v-model="email">
<p v-if="!$v.email.email">请输入正确的Email地址。</p>
<p v-if="!$v.email.required && $v.email.$dirty">Email地址不能为空。</p>
</div>
这样在完成输入,移动到下一个输入框的时候,就会提示错误。
一些其他种类的验证器
现在已经用过了required, email验证器,了解了基础工作原理,可以来看看在其他字段上运用过滤器了。
数字和大小值验证
为age输入框来添加验证器,想验证的是年龄必须大于18岁:
import {required, email, numeric, minValue } from 'vuelidate/lib/validators';
validations :{
email:{
required,
email
},
age :{
required,
numeric,
minValue:minValue(18)
}
}
这里导入了新的验证器,一个数字验证器,一个minValue,是一个函数,需要传入一个最小值。类似的验证器还有验证最大值,验证字符串的大小长度等。
现在再给age框添加上验证事件和UI提示:
<div class="input" :class="{invalid: $v.age.$error}">
<label for="age">Your Age</label>
<input
id="age"
@blur="$v.age.$touch()"
v-model="age">
<p v-if="!$v.age.required && $v.age.$dirty">年龄不能为空。</p>
<p v-if="!$v.age.numeric && $v.age.$dirty">必须输入数字。</p>
<p v-if="!$v.age.minValue && $v.age.$dirty &&$v.age.numeric">年龄必须大于{{ $v.age.$params.minValue.min }}岁。</p>
</div>
这里要注意的是,如果把input框的类型限定,比如type="number",会导致numeric的属性不会正常变化,所以一般需要验证的可以放开input的类型。
这里新加的内容就是获取这些带参数的验证器其中的参数,以动态的展示限制条件。详细的各个参数如何访问可以查看官方文档。
密码相等
常用的验证之一是验证两个密码是否相等。
这里需要使用一个新的验证器叫做sameAs
,这也是一个带参数的验证器,还有两种写法。
分成两步,第一步先给上边的密码输入框添加验证:
import {required, email, numeric, minValue, minLength } from 'vuelidate/lib/validators';
validations :{
......
password: {
required,
minLength: minLength(4)
}
}
UI就简单一些,不做提示只修改样式:
<div class="input" :class="{invalid: $v.password.$error}">
<label for="password">Password</label>
<input
type="password"
id="password"
@blur="$v.password.$touch()"
v-model="password">
</div>
第二步,就是给下边一个密码框添加验证相同密码的验证功能:
import {required, email, numeric, minValue, minLength, sameAs } from 'vuelidate/lib/validators';
confirmPassword: {
//直接传入字符串
// sameAs: sameAs("saner")
//传入当前实例的属性值
sameAs: sameAs(v=>v.password)
}
同样也设置上UI:
<div class="input" :class="{invalid: $v.confirmPassword.$error}">
<label for="confirm-password">Confirm Password</label>
<input
type="password"
id="confirm-password"
@blur="$v.confirmPassword.$touch()"
v-model="confirmPassword">
</div>
其他的验证都无需设置,因为会自动盯住上一个密码框的变化。
仅在特定情况下才进行验证
紧接着密码下边是一个选择国家的SELECT元素,假如我们有特定的要求,比如如果选中国家是德国,则不需要点击下边的Accept Terms of Use
选项。来看看如何实现:
import {required, email, numeric, minValue, minLength, sameAs, requiredUnless} from 'vuelidate/lib/validators';
terms: {
required: requiredUnless(v=>{
return v.country === 'germany'
})
}
这样就行了,很方便的搞定了。不过不知道为什么,在我的机器上checkbox的required不会变化,后来在stackflow上找到了答案:
新版本的Vuelidate中对CHECKBOX的验证修改了,未选中情况下的false
值默认也满足required
条件;但没有关系,反正绑定了v-model
,可以知道按钮选中与否,可以使用下边的代码来验证是否选中:
myCb : { sameAs: sameAs( () => true ) }
想达到和教程一样的效果,需要编写一个自定义验证器,同时也知道了自定义验证器的第二个参数传入的是当前的Vue实例对象:
terms: {
required: (val,vm)=>{
if ( vm.country === 'germany') {
return true;
}
return vm.terms === true;
}
},
验证数组
我们采用v-for
的形式渲染了hobbyInputs
系列控件,在把hobbyInputs
以JSON格式发送之前,如何验证整个数组的有效性。
答案是除了验证数组本身的长度等数据之外,还需要以嵌套的形式验证其内部的每一个数据。编写的验证稍微有些复杂,但很容易懂:
hobbyInputs :{
required,
minLength: minLength(1),
//$each表示当前array中的每一个元素
//所以其中要对这个元素的属性进行验证
//$each代表的是hobbyInput对象,要验证其value,验证器中的属性就是value
$each: {
value: {
required,
minLength:minLength(5)
}
}
}
$each
代表数组中的每一个元素,在这个例子中,就是hobbyInput
对象,等于我们要对hobbyInput
的value
属性再编写验证器,就写成了这种嵌套的。
然后需要将验证器添加到表单里:
<div class="hobby-list">
<div
class="input"
v-for="(hobbyInput, index) in hobbyInputs"
:key="hobbyInput.id">
<label :for="hobbyInput.id">Hobby #{{ index }}</label>
<input
type="text"
:id="hobbyInput.id"
@input="$v.hobbyInputs.$each[index].value.$touch()"
v-model="hobbyInput.value">
<button @click="onDeleteHobby(hobbyInput.id)" type="button">X</button>
</div>
<p v-if="!$v.hobbyInputs.minLength || !$v.hobbyInputs.required">You have to select at least {{ $v.hobbyInputs.$params.minLength.min }} hobby(s).</p>
</div>
注意看绑定的事件,需要给$each
加上一个索引,就是数组的索引,在v-for
中取得,然后指定验证字段value
,最后也加上$touch()
,其实和验证普通单个数据一样,多出来的是通过数组和索引选择到具体对象的过程。
在UI错误信息里,也可以通过$params
获取传入的参数。
修改提交按钮
一个很常见的情况,是有错误的情况下不允许提交表单。
实际上很简单,这个时候就可以用到$v.$invalid
,$v
的$invalid
在所有子验证全部的$invalid
都是false
的情况下返回false
,由于$invalid
不检测dirty
,所以使用这个比使用$error
要好。
<button :disabled="$v.$invalid" type="submit">Submit</button>
自定义验证器
既然是验证,肯定都会提供自定义验证器的功能。来看看Vuelidate的验证器如何编写。
所谓一个验证器,其实就是一个自定义的名称,然后对应一个接受一个参数并且返回一个布尔值的函数。
给email
加自定义验证器:
email: {
required,
email,
myVal: val => {
return false
}
},
可以打印出$v
或者用Chrome的Vue开发工具查看,$v.email
下边多了一个myVal
,一直返回false
,导致$invalid
永远为true
,不会通过任何验证。
那么就可以来编写任意的条件了,比如:
email: {
required,
email,
myVal: val => {
return val !== 'test@test.com'
}
},
如果邮件名等于这个,就无法通过验证。
自定义验证器的功能远不止如此。
异步验证
在开发中常见的是,用户输入之后,异步查询服务器然后告诉用户是否可用。
自定义验证器还可以返回一个Promise对象,Vuelidate会自动根据返回结果来判断验证通过与否。来模拟一个异步效果:
myVal: val => {
if (val === '') return true
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(val === 'test@test.com')
}, 2000)
})
}
返回了一个两秒钟之后返回val
是否等于test@test.com
的结果。
启动项目,在地址栏输入test@test.com
,myVal
会过两秒才会变成true
。
注意观察Chrome的Vue插件,可以发现在这个过程中,前边一直没有用到的$pending
变成了true
,在返回异步请求之后,$pending
又会变回false
。
这实际上就是异步的验证,在异步验证没有返回结果之前,$pending
会变成true
。
有了模拟的异步之后,就可以在其中使用axios向后端查询实现各种验证,比如验证该用户名是否已经被注册。具体的实现就需要后端配合了。