Vuelidate 在一小时内完成表单验证

Avatar of Sarah Drasner
Sarah Drasner

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 $200 免费信用额度!

表单验证因其难以实现而闻名。在本教程中,我们将分解问题以减轻部分痛苦。创建用于表单的良好抽象是 Vue.js 的强项,而 Vuelidate 是我个人最喜欢的验证选项,因为它不需要太多麻烦。此外,它非常灵活,因此我们甚至不必按照我在这里介绍的方式来执行它。这只是一个起点。

如果您只想复制和粘贴我的完整工作示例,它在最后。随便吧。我不会说出来的。那么你花在上面的时间肯定不到一个小时,更像是两分钟,对吧?!啊,互联网真是一个美丽的地方。

您可能会发现需要修改我们在本文中使用的表单,在这种情况下,您可以阅读全文。我们将从一个简单的案例开始,逐步构建更具体的示例。最后,我们将介绍如何在用户完成表单后显示表单错误。

最简单的案例:完成输入后显示条目

首先,让我们看看如何使用 Vuelidate。我们需要创建一个名为 validations 的对象,它将反映我们要在表单中捕获的数据结构。最简单地说,它看起来像这样

data: {
  name: ‘’
},
validations: {
  name: {
    required
  }
}

这将在计算属性中创建一个对象,我们可以使用 $v 找到它。它在 Vue DevTools 中看起来像这样

computed properties when empty

这里需要注意几件事:$v 是一个计算属性。这很棒,因为这意味着它会被缓存,直到有东西更新,这是一种处理这些状态更改的高效方法。如果您想了解更多关于这个概念的信息,请查看 我的文章

需要注意的另一件事是:有两个对象——一个关于所有验证的通用对象(目前只有一个)和一个关于属性 name 的特定对象。这很棒,因为如果我们正在寻找所有字段的通用信息,我们拥有这些信息。如果我们需要收集特定数据,我们也拥有这些数据。

让我们看看当我们开始在那个输入框中键入时会发生什么

random typing shown in computed properties

我们可以在 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.namerequired 来查看它是否正确评估以及它是否已提供。我们还可以查看字符长度是否超过最小值。我们甚至有一个 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 对象中创建 password1password2(我们将在稍后使用它们来验证匹配的密码),并从验证器中导入所需内容。

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 校对本文。