點燈坊

失くすものさえない今が強くなるチャンスよ

使用 Vuelidate 驗證 Form

Sam Xiao's Avatar 2021-04-15

Vue 雖然也能搭配 HTML 5 的 Form Validation,但 Vue 的 Model-based 與 HTML 的 Element-based 則大異其趣,Model-based 更直覺且可讀性更高。

Version

Vue 2.6.11
Vuelidate 0.7.5

HTML 5

overview000

當沒有任何輸入時,按下 Submit 會出現警告訊息。

overview001

當輸入字串長度小於 5 時,按下 Submit 也會出現警告訊息。

overview002

當有輸入字串且長度大於 5 時,才會觸發原本的 submit event。

<template>
  <form @submit.prevent="onSubmit">
    <input ref="username" @invalid.capture.prevent="onInvalid" required minlength="5">
    <button>Submit</button>
    <p>{{ msg }}</p>
  </form>
</template>

<script>
let onSubmit = function() {
  this.msg = 'Submit event fired'
}

let onInvalid = function() {
  if (this.$refs.username.validity.valueMissing)
    this.msg = 'User name is required'
  else if (this.$refs.username.validity.tooShort)
    this.msg = 'User name is too short'
}

export default {
  name: "App",
  data: () => ({
    msg: ''
  }),
  methods: {
    onSubmit,
    onInvalid
  }
}
</script>

第 2 行

<form @submit.prevent="onSubmit">
  <input ref="username" @invalid.capture.prevent="onInvalid" required minlength="5">
  <button>Submit</button>
  <p>{{ msg }}</p>
</form>

若要使用 HTML 5 Form Validation,則 <input> 必須放在 <form> 內,且不能使用 <button>click event,而要使用 <form>submit event。

<form @submit.prevent="onSubmit">

<form>submit event 預設會 post 到 server,因為現在會使用 Ajax 打 RESTful API,因此會在 @submit 加上 prevent modifier 避免 <form> post 到 server,相當於 event.preventDefault()

<input ref="username" @invalid.capture.prevent="onInvalid" required minlength="5">

由於我們想判斷 <input>必填長度需大於 5,使用了 HTML 5 的 requiredminlength="5" attribute。

HTML 5 判斷時會觸發 invalid event,且會在 <input> 以 popup 顯示預設錯誤訊息,但因為我們只想借用 HTML 5 Form Validation,並不想以 popup 顯示,因此使用了 invalid.capture.prevent

  • capture:原本的 HTML 5 會先由內而外觸發 popup event 然後才是 invalid event,為了阻擋 popup 顯示,特別使用 capture modifier 改成由外向內先觸發 invalid event,如此才能避免 popup 顯示。
  • prevent:為了避免 invalid event 執行完又觸發 popup event,因此使用 prevent modifier 避免 invalid event 執行完後繼續執行 popup event。

HTML 5 主要以 element 控制,而非 Vue 的 model-based 寫法,因此特別加上 ref="username",稍後將以 username 控制 <input> element。

14 行

let onInvalid = function() {
  if (this.$refs.username.validity.valueMissing)
    this.msg = 'User name is required'
  else if (this.$refs.username.validity.tooShort)
    this.msg = 'User name is too short'
}

HTML 5 允許我們由 element 的 validity.valueMissingvalidity.tooShort 判斷 requiredminlength="5" 是否成立。

由於 validity 是在 <input> element 下,因此必須透過 this.$refs.username 取得 <input>

第 10 行

let onSubmit = function() {
  this.msg = 'Submit event fired'
}

invalid event 都沒錯時,才會執行 submit event 完成 Form Validation。

可發現 HTML 5 Form Validation 寫法非常繁瑣,除了須小心控制 event 外,還必須小心處理 element,更可發現 Vue 的 model-based 寫法之精簡

Add Vuelidate

$ yarn add vuelidate

使用 Yarn 安裝 vuelidate

Main

main.js

import Vue from 'vue'
import App from './App.vue'
import Vuelidate from "vuelidate"

Vue.use(Vuelidate)

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

第 3 行

import Vuelidate from "vuelidate"

引用 vuelidate

第 5 行

Vue.use(Vuelidate)

Vue 使用 Vuelidate。

Component

App.vue

<template>
  <div>
    <input v-model="username">
    <button @click="onClick">Submit</button>
    <p>{{ msg }}</p>
  </div>
</template>

<script>
import { required, minLength } from 'vuelidate/lib/validators'

let onClick = function() {
  if (!this.$v.username.required)
    this.msg = 'User name is required'
  else if (!this.$v.username.minLength)
    this.msg = 'User name is too short'
  else
    this.msg = 'Submit event fired'
}

export default {
  name: 'App',
  data: () => ({
    username: '',
    msg: ''
  }),
  validations: {
    username: {
      required,
      minLength: minLength(5)
    }
  },
  methods: {
    onClick
  }
}
</script>

第 3 行

<input v-model="username">
<button @click="onClick">Submit</button>
<p>{{ msg }}</p>

<input> 使用 v-model 綁定到 username model,<button> 則將 click event 綁定到 onClick()

我們可發現在 Vue 使用相當直覺,不必將 <input> 包在 <form> 裡,也不必改用 <form>submit event 與 prevent modifier,更不必在 invalid event 使用較罕用的 capture modifier,就如同一般 HTML 用法

23 行

data: () => ({
  username: '',
  msg: ''
}),

由於 Vue 為 model-based 寫法,因此要宣告 <input> 所綁定的 username model。

這也與 HTML 的 element-based 寫法不一樣,不必如 HTML 要搭配 ref attribute 設定 element 名稱。

27 行

validations: {
  username: {
    required,
    minLength: minLength(5)
  }
},

新增 validations property,在其內宣告與 model 相同名稱的 username property,並將要驗證的 requiredminLength 組合成 object。

這也與 HTML 以 attribute 設定驗證功能不一樣,而是入境隨俗以 model-based 方式,改為對 model 驗證,而不是對 element 驗證

第 10 行

import { required, minLength } from 'vuelidate/lib/validators'

requiredminLength 則以 function 提供,因此要從 vuelidate/lib/validators 引用。

HTML 的驗證功能是 attribute;Vuelidate 則是 function

12 行

let onClick = function() {
  if (!this.$v.username.required)
    this.msg = 'User name is required'
  else if (!this.$v.username.minLength)
    this.msg = 'User name is too short'
  else
    this.msg = 'Submit event fired'
}

this.$v 下的 model 的 requiredminLength property 判斷,true 則通過驗證,false 則失敗,非常直覺。

HTML 必須透過 element 下的 validity 判斷,因此要使用 ref 設定 element 名稱,且 valueMissingtooShort 又與 requiredminLength 不一樣;Vuelidate 則在 this.$v 使用 model,且一樣是 requiredminLength,非常直覺

Conclusion

  • 可發現 HTML 5 的 element-based 寫法非常 tricky 也不直覺,但 Vuelidate 則充分利用 Vue 的 model-based 特性,一切都很直覺,可讀性又高

Reference

Learn Vue, Getting Smart with Vue Form Validation - Vuelidate Tutorial