表单验证因其难以实现而闻名。在本教程中,我们将分解问题以减轻部分痛苦。创建用于表单的良好抽象是 Vue.js 的强项,而 Vuelidate 是我个人最喜欢的验证选项,因为它不需要太多麻烦。此外,它非常灵活,因此我们甚至不必按照我在这里介绍的方式来执行它。这只是一个起点。
如果您只想复制和粘贴我的完整工作示例,它在最后。随便吧。我不会说出来的。那么你花在上面的时间肯定不到一个小时,更像是两分钟,对吧?!啊,互联网真是一个美丽的地方。
您可能会发现需要修改我们在本文中使用的表单,在这种情况下,您可以阅读全文。我们将从一个简单的案例开始,逐步构建更具体的示例。最后,我们将介绍如何在用户完成表单后显示表单错误。
最简单的案例:完成输入后显示条目
首先,让我们看看如何使用 Vuelidate。我们需要创建一个名为 validations
的对象,它将反映我们要在表单中捕获的数据结构。最简单地说,它看起来像这样
data: {
name: ‘’
},
validations: {
name: {
required
}
}
这将在计算属性中创建一个对象,我们可以使用 $v
找到它。它在 Vue DevTools 中看起来像这样

这里需要注意几件事:$v
是一个计算属性。这很棒,因为这意味着它会被缓存,直到有东西更新,这是一种处理这些状态更改的高效方法。如果您想了解更多关于这个概念的信息,请查看 我的文章。
需要注意的另一件事是:有两个对象——一个关于所有验证的通用对象(目前只有一个)和一个关于属性 name
的特定对象。这很棒,因为如果我们正在寻找所有字段的通用信息,我们拥有这些信息。如果我们需要收集特定数据,我们也拥有这些数据。
让我们看看当我们开始在那个输入框中键入时会发生什么

我们可以在 data
中看到我们有……嗯,我像个疯子一样在打字。但让我们检查一下其他一些字段。$dirty
在这种情况下,指的是表单是否已被触碰。我们还可以看到 $model
字段现在已为 name 对象填充,这与 data
中的内容相呼应。
$error
和 $invalid
听起来一样,但实际上有点不同。$invalid
检查它是否通过验证,但 $error
检查 两者:$invalid
是否为 true
以及它是否为 $dirty
(表单是否已被触碰)。如果这一切听起来都难以理解(哈哈,明白了吧?),不用担心,我们将逐步讲解这些内容。
安装 Vuelidate 并创建第一个表单验证
好吧,那是一个非常简单的例子。让我们用它来构建一些真实的东西。我们将把它引入我们的应用程序,这次我们将使该字段为必填项 并 给它一个最小长度要求。在 Vue 应用程序中,我们将首先添加 Vuelidate
yarn add vuelidate
现在,让我们进入 main.js
文件并将其更新如下
import Vue from 'vue';
import Vuelidate from "vuelidate";
import App from './App.vue';
import store from './store';
Vue.use(Vuelidate);
Vue.config.productionTip = false
new Vue({
store,
render: h => h(App)
}).$mount('#app')
现在,在包含表单元素的任何组件中,让我们首先导入所需的验证器
import { required, minLength } from 'vuelidate/lib/validators'
然后,我们将数据放在一个函数中,以便我们可以重用该组件。你可能知道这一点。接下来,我们将 name
表单字段放在一个对象中,因为通常情况下,我们想将所有表单数据一起捕获。
我们还需要包含验证,它将反映我们的数据。我们将再次使用 required
,但这次我们还将添加一个用于最小字符长度的键值对,minLength(x)
,它看起来像这样
<script>
import { required, minLength } from 'vuelidate/lib/validators'
export default {
data() {
return {
formResponses: {
name: '',
}
}
},
validations: {
formResponses: {
name: {
required,
minLength: minLength(2)
},
}
}
}
</script>
接下来,在模板中,我们将为可访问性目的创建一个标签。我们不会使用数据中的内容来创建 v-model
中的关系,而是使用我们在 validations
对象中看到的那个计算属性($model
)。
<template>
<div id="app">
<label for="fname">Name*</label>
<input id="fname" class="full" v-model="$v.formResponses.name.$model" type="text">
</div>
</template>
最后,在表单输入下方,我们将在表单下方放置一些文本。我们可以使用附加到 formResponses.name
的 required
来查看它是否正确评估以及它是否已提供。我们还可以查看字符长度是否超过最小值。我们甚至有一个 params
对象,它将告诉我们我们指定的字符数。我们将使用所有这些来为用户创建信息丰富的错误消息。
<p class="error" v-if="!$v.formResponses.name.required">this field is required</p>
<p class="error" v-if="!$v.formResponses.name.minLength">Field must have at least {{ $v.formResponses.name.$params.minLength.min }} characters.</p>
我们将为我们的错误类设置样式,以便一目了然地知道它们是错误。
.error {
color: red;
}
偷点懒
您可能在最后一个演示中注意到,错误会立即出现并在键入时更新。就我个人而言,我不喜欢以这种方式显示表单验证,因为我认为这会分散注意力并造成混淆。我喜欢做的是等到键入完成再进行评估。对于这种交互,Vue 配备了 v-model
的修饰符:v-model.lazy
。这将只在用户完成输入的任务后评估双向绑定。
现在我们可以像这样改进我们的单个表单输入
<label for="fname">Name*</label>
<input id="fname" class="full" v-model.lazy="$v.formResponses.name.$model" type="text">
创建自定义验证器
Vuelidate 内置了许多验证器,这非常有用。但是,有时我们需要一些更自定义的东西。让我们为强密码创建一个自定义验证器,并检查它是否与 Vuelidate 的 sameAs
验证器匹配
我们将首先创建一个附加到输入框的标签,输入框将为 type="password"
。
<section>
<label for="fpass1">Password*</label>
<input id="fpass1" v-model="$v.formResponses.password1.$model" type="password">
</section>
在我们的数据中,我们将在 formResponses
对象中创建 password1
和 password2
(我们将在稍后使用它们来验证匹配的密码),并从验证器中导入所需内容。
import { required, minLength, email, sameAs } from "vuelidate/lib/validators";
export default {
data() {
return {
formResponses: {
name: null,
email: null,
password1: null,
password2: null
}
};
},
然后,我们将创建自定义验证器。在下面的代码中,您可以看到我们正在使用正则表达式进行不同类型的评估。我们将创建一个 strongPassword
方法,传入我们的 password1
,然后我们可以使用 .test()
以几种方式检查它,它的工作原理与您预期的一样:如果它通过了,则必须返回 true
,如果没有通过,则返回 false
。
validations: {
formResponses: {
name: {
required,
minLength: minLength(3)
},
email: {
required,
email
},
password1: {
required,
strongPassword(password1) {
return (
/[a-z]/.test(password1) && // checks for a-z
/[0-9]/.test(password1) && // checks for 0-9
/\W|_/.test(password1) && // checks for special char
password1.length >= 6
);
}
},
}
我将每行分开,以便您可以看到发生了什么,但我们也可以将整个代码写成一行,如下所示
const regex = /^[a-zA-Z0-9!@#\$%\^\&*\)\(+=._-]{6,}$/g
我更喜欢将其分解,因为更容易修改。
这使我们能够为我们的验证创建错误文本。我们可以让它说任何我们喜欢的东西,甚至可以将其从 v-if
中移除并使其出现在页面上。由你决定!
<section>
<label for="fpass1">Password*</label>
<input id="fpass1" v-model="$v.formResponses.password1.$model" type="password">
<p class="error" v-if="!$v.formResponses.password1.required">this field is required</p>
<p class="error" v-if="!$v.formResponses.password1.strongPassword">Strong passwords need to have a letter, a number, a special character, and be more than 8 characters long.</p>
</section>
现在我们可以使用 Vuelidate 的 sameAs
方法检查第二个密码是否与第一个密码匹配
validations: {
formResponses: {
password1: {
required,
strongPassword(password1) {
return (
/[a-z]/.test(password1) && // checks for a-z
/[0-9]/.test(password1) && // checks for 0-9
/\W|_/.test(password1) && // checks for special char
password1.length >= 6
);
}
},
password2: {
required,
sameAsPassword: sameAs("password1")
}
}
}
我们可以创建第二个密码字段
<section>
<label for="fpass2">Please re-type your Password</label>
<input id="fpass2" v-model="$v.formResponses.password2.$model" type="password">
<p class="error" v-if="!$v.formResponses.password2.required">this field is required</p>
<p class="error" v-if="!$v.formResponses.password2.sameAsPassword">The passwords do not match.</p>
</section>
现在您可以看到整个代码都在一起运行
在完成时评估
您可以看到最后一个示例在表单完成之前是多么嘈杂。在我看来,一个更好的方法是在整个表单完成时进行评估,这样用户就不会在过程中被打断。以下是如何做到这一点。
还记得我们之前查看的 $v
计算属性吗?它包含所有单个属性的对象,但也包含所有验证的对象。在里面,有三个非常重要的值
$anyDirty
:表单是否被触碰过或留空$invalid
:表单中是否存在任何错误$anyError
:如果存在任何错误(即使只有一个),这将评估为true
您可以使用 $invalid
,但我更喜欢 $anyError
,因为它不需要我们检查它是否为脏的。
让我们改进最后一个表单。我们将添加一个提交按钮,以及一个 uiState
字符串来跟踪,嗯,UI 状态!这非常有用,因为我们可以跟踪我们是否尝试过提交,以及我们是否准备好发送我们收集到的内容。我们还将进行一个小的样式改进:将错误定位到表单上,这样它就不会为了显示错误而四处移动。
首先,让我们添加几个新的数据属性
data() {
return {
uiState: "submit not clicked",
errors: false,
empty: true,
formResponses: {
...
}
}
}
现在,我们将在表单末尾添加一个提交按钮。@click
指令末尾的 .prevent
修饰符类似于 preventDefault
,它会阻止页面重新加载
<section>
<button @click.prevent="submitForm" class="submit">Submit</button>
</section>
我们在 submitForm
方法中处理一些不同的状态。 我们将使用 Vuelidate 中的计算属性 ($anyDirty
) 来查看表单是否为空。 请记住,我们可以从 this.$v
中收集这些信息。 我们使用 formResponses
对象来保存所有表单响应,因此我们将使用 this.$v.formResponses.$anyDirty
。 我们将该值映射到我们的“empty”数据属性。 我们也会对错误做同样的操作,并将 uiState
更改为 "submit clicked"
submitForm() {
this.formTouched = !this.$v.formResponses.$anyDirty;
this.errors = this.$v.formResponses.$anyError;
this.uiState = "submit clicked";
if (this.errors === false && this.formTouched === false) {
//this is where you send the responses
this.uiState = "form submitted";
}
}
如果表单没有错误并且不为空,我们将发送响应并将 uiState
更改为“form submitted”。
现在,我们可以处理一些错误和空状态,最后,如果表单已提交,我们将评估成功。
<section>
<button @click.prevent="submitForm" class="submit">Submit</button>
<p v-if="errors" class="error">The form above has errors,
<br>please get your act together and resubmit
</p>
<p v-else-if="formTouched && uiState === 'submit clicked'" class="error">The form above is empty,
<br>cmon y'all you can't submit an empty form!
</p>
<p v-else-if="uiState === 'form submitted'" class="success">Hooray! Your form was submitted!</p>
</section>
在这个表单中,我们为每个部分指定了相对定位,并在底部添加了一些填充。 这将允许我们为错误状态提供绝对定位,这将阻止表单移动。
.error {
color: red;
font-size: 12px;
position: absolute;
text-transform: uppercase;
}
我们还需要做最后一件事:现在我们已经将错误绝对地放置在表单中,除非我们将它们彼此相邻放置,否则它们会堆叠在一起。 我们还想要检查表单是否处于错误状态,这只有在提交按钮被点击后才会为真。 这可能是一种有效的方法 - 我们只在用户完成表单后显示错误,这可能不那么具有侵入性。 你可以选择以这种方式进行,也可以使用上一节中使用的 v-model.lazy
示例。
我们之前的错误看起来像这样
<section>
...
<p class="error" v-if="!$v.formResponses.password2.required">this field is required</p>
<p class="error" v-if="!$v.formResponses.password2.sameAsPassword">The passwords do not match.</p>
</section>
现在,它们将像这样包含在一起
<p v-if="errors" class="error">
<span v-if="!$v.formResponses.password1.required">this field is required.</span>
<span v-if="!$v.formResponses.password1.strongPassword">Strong passwords need to have a letter, a number, a special character, and be more than 8 characters long.</span>
</p>
为了让事情对你来说更简单,有一个库可以动态地找出要显示的错误,它基于你的验证。 太酷了! 如果你做的是简单的事情,它可能负担过重,但如果你有一个非常复杂的表单,它可能会为你节省时间 :)
就这样! 我们的表单已验证,我们有错误和空状态,但我们在输入时没有。
衷心感谢 Vuelidate 的维护者之一 Damian Dulisz 校对本文。
通过编程方式将错误与字段关联的一种便捷方法是使用 aria-describedby 属性,这样就不必仅依靠距离来将错误与字段关联起来。 将此属性放在输入字段上并通过唯一 ID 指向错误消息,将为表单验证模式添加更多可访问性。 当屏幕阅读器将焦点放在输入字段上时,它将宣布表单标签、输入类型和关联的错误。
另一种方法是将整个字段和错误包装在显式标签中,但设计并不总是允许这样做,因此 aria-describedby 在这里很有帮助。 :)
那个一劳永逸的密码-regexp 不等于三个不同的密码,因为它不检查不同的字符类。 此外,还有 6 个字符和 8 个字符的要求。 另外,大写字母不足以满足 strongPasswordTest 的要求。
抛开所有这些小问题,这是一个很棒的工具介绍,它将在不久的将来对我有很大帮助。 谢谢!
虽然它看起来很方便,但始终在服务器端进行验证更明智,因为这些数据总是在提交之前被黑客攻击。 我认为客户端验证只适用于简单的验证,以节省时间,而 HTML 的“required”属性可以完成大部分工作。
我正在使用 Vuelidate 和 NuxtJS。 很简单,而且效果很好。
您好! 首先感谢您。
您可以添加一个 requiredIf 的示例吗?
例如,表单中的密码只有在输入新用户时才需要,更新现有用户时不需要。