表单校验

基本的示例

Watch a free lesson on Vue School

表单校验是浏览器原生支持的,但是有的时候用不同的浏览器处理起来需要一些小技巧。即使当表单校验已经被完美支持,你也还是有很多时候需要进行自定义的校验。这时一个更加手动的基于 Vue 的解决方案可能会更适合。我们来看一个简单的示例。

给定一个表单,包含三个字段,其中两个是必填项。我们先来看看 HTML:

  1. <form
  2. id="app"
  3. @submit="checkForm"
  4. action="https://vuejs.org/"
  5. method="post"
  6. >
  7. <p v-if="errors.length">
  8. <b>Please correct the following error(s):</b>
  9. <ul>
  10. <li v-for="error in errors">{{ error }}</li>
  11. </ul>
  12. </p>
  13. <p>
  14. <label for="name">Name</label>
  15. <input
  16. id="name"
  17. v-model="name"
  18. type="text"
  19. name="name"
  20. >
  21. </p>
  22. <p>
  23. <label for="age">Age</label>
  24. <input
  25. id="age"
  26. v-model="age"
  27. type="number"
  28. name="age"
  29. min="0"
  30. >
  31. </p>
  32. <p>
  33. <label for="movie">Favorite Movie</label>
  34. <select
  35. id="movie"
  36. v-model="movie"
  37. name="movie"
  38. >
  39. <option>Star Wars</option>
  40. <option>Vanilla Sky</option>
  41. <option>Atomic Blonde</option>
  42. </select>
  43. </p>
  44. <p>
  45. <input type="submit" value="Submit">
  46. </p>
  47. </form>

我们从头到尾看一遍,这个 <form> 标记上有一个我们将会用在 Vue 组件上的 ID。这里有一个你稍后会看到的 submit 处理函数,而这里的 action 是一个可能指向了某个真实服务器的临时 URL (当然你在服务端也是要有校验的)。

下面有一段内容,会根据错误状态进行显示或隐藏。它将会在表单的最顶端渲染一个简单的错误列表。同时要注意我们会在提交的时候进行校验,而不是每个字段被修改的时候。

最后值得注意的是这三个字段都有一个对应的 v-model 来连接它们的值,我们将会在 JavaScript 中使用它。现在我们就来看一下。

  1. const app = new Vue({
  2. el: '#app',
  3. data: {
  4. errors: [],
  5. name: null,
  6. age: null,
  7. movie: null
  8. },
  9. methods:{
  10. checkForm: function (e) {
  11. if (this.name && this.age) {
  12. return true;
  13. }
  14. this.errors = [];
  15. if (!this.name) {
  16. this.errors.push('Name required.');
  17. }
  18. if (!this.age) {
  19. this.errors.push('Age required.');
  20. }
  21. e.preventDefault();
  22. }
  23. }
  24. })

非常短小精悍。我们定义了一个数组来放置错误,并将这三个表单字段的默认值设为 nullcheckForm 的逻辑 (在表单提交时运行) 只会检查姓名和年龄,因为电影是选填的。如果它们是空的,那么我们会检查每一个字段并设置相应的错误,差不多就是这样。你可以在下面运行这个 demo。不要忘记提交成功时它会 POST 到一个临时的 URL。

CodePen 查看 Raymond Camden (@cfjedimaster) 的 表单校验 1

使用自定义校验

对于第二个示例来说,第二个文本字段 (年龄) 变换成了电子邮件地址,它将会通过一些自定义的逻辑来校验。这部分代码来自 StackOverflow 的问题:如何在 JavaScript 中校验电子邮件地址。这是一个很好的问题,因为它会让 Facebook 上最激烈的政治、宗教争论看上去都只是“哪家的啤酒最好喝”这样的小分歧了。讲真的这很疯狂。我们来看 HTML,尽管它和第一个例子很接近。

  1. <form
  2. id="app"
  3. @submit="checkForm"
  4. action="https://vuejs.org/"
  5. method="post"
  6. novalidate="true"
  7. >
  8. <p v-if="errors.length">
  9. <b>Please correct the following error(s):</b>
  10. <ul>
  11. <li v-for="error in errors">{{ error }}</li>
  12. </ul>
  13. </p>
  14. <p>
  15. <label for="name">Name</label>
  16. <input
  17. id="name"
  18. v-model="name"
  19. type="text"
  20. name="name"
  21. >
  22. </p>
  23. <p>
  24. <label for="email">Email</label>
  25. <input
  26. id="email"
  27. v-model="email"
  28. type="email"
  29. name="email"
  30. >
  31. </p>
  32. <p>
  33. <label for="movie">Favorite Movie</label>
  34. <select
  35. id="movie"
  36. v-model="movie"
  37. name="movie"
  38. >
  39. <option>Star Wars</option>
  40. <option>Vanilla Sky</option>
  41. <option>Atomic Blonde</option>
  42. </select>
  43. </p>
  44. <p>
  45. <input
  46. type="submit"
  47. value="Submit"
  48. >
  49. </p>
  50. </form>

尽管这里的不同点很小,注意顶端的 novalidate="true"。但是这很重要,因为浏览器会尝试在 type="email" 的字段校验邮件地址。坦白说在这个案例中浏览器的校验规则是值得信任的,不过我们想要创建一个自定义校验的例子,所以把它禁用了。以下是更新后的 JavaScript。

  1. const app = new Vue({
  2. el: '#app',
  3. data: {
  4. errors: [],
  5. name: null,
  6. email: null,
  7. movie: null
  8. },
  9. methods: {
  10. checkForm: function (e) {
  11. this.errors = [];
  12. if (!this.name) {
  13. this.errors.push("Name required.");
  14. }
  15. if (!this.email) {
  16. this.errors.push('Email required.');
  17. } else if (!this.validEmail(this.email)) {
  18. this.errors.push('Valid email required.');
  19. }
  20. if (!this.errors.length) {
  21. return true;
  22. }
  23. e.preventDefault();
  24. },
  25. validEmail: function (email) {
  26. var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
  27. return re.test(email);
  28. }
  29. }
  30. })

如你所见,我们添加了一个新方法 validEmail,它将会在 checkForm 中被调用了。我们现在可以这样运行示例:

CodePen 查看 Raymond Camden (@cfjedimaster) 的 表单校验 2

另一个自定义校验的例子

在第三个示例中,我们已经构建了一些你可能在一些调研类应用中见过的东西。用户需要花掉“预算”来为歼星舰模型装配一套部件。总价必须等于 100。先看 HTML。

  1. <form
  2. id="app"
  3. @submit="checkForm"
  4. action="https://vuejs.org/"
  5. method="post"
  6. novalidate="true"
  7. >
  8. <p v-if="errors.length">
  9. <b>Please correct the following error(s):</b>
  10. <ul>
  11. <li v-for="error in errors">{{ error }}</li>
  12. </ul>
  13. </p>
  14. <p>
  15. Given a budget of 100 dollars, indicate how much
  16. you would spend on the following features for the
  17. next generation Star Destroyer. Your total must sum up to 100.
  18. </p>
  19. <p>
  20. <input
  21. v-model.number="weapons"
  22. type="number"
  23. name="weapons"
  24. > Weapons <br/>
  25. <input
  26. v-model.number="shields"
  27. type="number"
  28. name="shields"
  29. > Shields <br/>
  30. <input
  31. v-model.number="coffee"
  32. type="number"
  33. name="coffee"
  34. > Coffee <br/>
  35. <input
  36. v-model.number="ac"
  37. type="number"
  38. name="ac"
  39. > Air Conditioning <br/>
  40. <input
  41. v-model.number="mousedroids"
  42. type="number"
  43. name="mousedroids"
  44. > Mouse Droids <br/>
  45. </p>
  46. <p>
  47. Current Total: {{total}}
  48. </p>
  49. <p>
  50. <input
  51. type="submit"
  52. value="Submit"
  53. >
  54. </p>
  55. </form>

这组输入框覆盖了五个不同的部件。注意这里为 v-model 特性添加了 .number。它会告诉 Vue 将其值作为数字来使用。不过这里有一个小小的 bug,那就是当其值为空的时候,它会回到字符串格式,稍后你将会看到变通的办法。为了让用户使用起来更方便,我们添加展示了一个当前的总和,这样我们就能够实时的看到它们一共花掉了多少钱。现在我们来看看 JavaScript。

  1. const app = new Vue({
  2. el: '#app',
  3. data:{
  4. errors: [],
  5. weapons: 0,
  6. shields: 0,
  7. coffee: 0,
  8. ac: 0,
  9. mousedroids: 0
  10. },
  11. computed: {
  12. total: function () {
  13. // 必须解析,因为 Vue 会将空值转换为字符串
  14. return Number(this.weapons) +
  15. Number(this.shields) +
  16. Number(this.coffee) +
  17. Number(this.ac+this.mousedroids);
  18. }
  19. },
  20. methods:{
  21. checkForm: function (e) {
  22. this.errors = [];
  23. if (this.total != 100) {
  24. this.errors.push('Total must be 100!');
  25. }
  26. if (!this.errors.length) {
  27. return true;
  28. }
  29. e.preventDefault();
  30. }
  31. }
  32. })

我们将总和设置为了一个计算属性,从那个我们解决掉的 bug 外面看上去,这已经足够了。我的 checkForm 方法现在只需要关注总和是不是 100 了。你可以在这里试用:

CodePen 查看 Raymond Camden (@cfjedimaster) 的 表单校验3

服务端校验

在我们最终的示例中,我们构建了一些用到 Ajax 的服务端校验的东西。这个表单将会问你为一个新产品起名字,并且将会确保这个名字是唯一的。我们快速写了一个 OpenWhisk 的 serverless action 来进行这个校验。虽然这不是非常重要,但其逻辑如下:

  1. function main(args) {
  2. return new Promise((resolve, reject) => {
  3. // 不好的产品名:vista, empire, mbp
  4. const badNames = ['vista', 'empire', 'mbp'];
  5. if (badNames.includes(args.name)) {
  6. reject({error: 'Existing product'});
  7. }
  8. resolve({status: 'ok'});
  9. });
  10. }

基本上除了“vista”、“empire”和“mbp”的名字都是可以接受的。好,让我们来看看表单。

  1. <form
  2. id="app"
  3. @submit="checkForm"
  4. method="post"
  5. >
  6. <p v-if="errors.length">
  7. <b>Please correct the following error(s):</b>
  8. <ul>
  9. <li v-for="error in errors">{{ error }}</li>
  10. </ul>
  11. </p>
  12. <p>
  13. <label for="name">New Product Name: </label>
  14. <input
  15. id="name"
  16. v-model="name"
  17. type="text"
  18. name="name"
  19. >
  20. </p>
  21. <p>
  22. <input
  23. type="submit"
  24. value="Submit"
  25. >
  26. </p>
  27. </form>

这里没有任何特殊的东西。接下来我们再看看 JavaScript。

  1. const apiUrl = 'https://openwhisk.ng.bluemix.net/api/v1/web/rcamden%40us.ibm.com_My%20Space/safeToDelete/productName.json?name=';
  2. const app = new Vue({
  3. el: '#app',
  4. data: {
  5. errors: [],
  6. name: ''
  7. },
  8. methods:{
  9. checkForm: function (e) {
  10. e.preventDefault();
  11. this.errors = [];
  12. if (this.name === '') {
  13. this.errors.push('Product name is required.');
  14. } else {
  15. fetch(apiUrl + encodeURIComponent(this.name))
  16. .then(res => res.json())
  17. .then(res => {
  18. if (res.error) {
  19. this.errors.push(res.error);
  20. } else {
  21. // 在成功的时候重定向到一个新的 URL 或做一些别的事情
  22. alert('ok!');
  23. }
  24. });
  25. }
  26. }
  27. }
  28. })

我们从一个运行在 OpenWhisk 的 API 的 URL 变量开始。现在注意 checkForm。在这个版本中,我们始终阻止了表单的提交 (当然,它也可以通过 Vue 在 HTML 中完成)。你可以看到一个基本的校验,即 this.name 是否为空,然后我们请求这个 API。如果名字是无效的,我们就添加一个错误。如果是有效的,我们就不做任何事 (只是一个 alert),但是你可以引导用户去一个新页面,在 URL 中带上产品的名字,或者其它行为。接下来你可以体验这个 demo:

CodePen 查看 Raymond Camden (@cfjedimaster) 的 表单校验4。

其它替代模式

这份秘笈专注在“手动”校验表单,当然一些非常棒的 Vue 的库会为你搞定这些事情。使用一些预打包的库可能会影响你的应用最终的体积,但是好处是非常多的。这里有经过充分测试且保持日常更新的代码。其中包括以下 Vue 的表单校验库: