# 🏅 Day 21 - VeeValidate 表單驗證套件運用
## 今日學習目標
- 學習將 VeeValidate 整合至 Nuxt3
- 學習如何在 Nuxt3 中使用 VeeValidate 的元件、驗證規則以及多國語系功能
## **安裝 VeeValidate**
在 Nuxt3 中使用 **VeeValidate** 進行表單驗證,需要安裝以下三個套件:
- `@vee-validate/nuxt` : 提供與 Nuxt 整合的功能,包含自動匯入元件和組合函式(composables)。
- `@vee-validate/rules`:提供內建的驗證規則,如必填、Email 格式等。
- `@vee-validate/i18n` :提供驗證訊息多國語系的支援。
### **安裝** @vee-validate/nuxt
VeeValidate 官方提供了支援 Nuxt 的 [**@vee-validate/nuxt** ](https://vee-validate.logaretm.com/v4/integrations/nuxt/) 模組,可以自動匯入 VeeValidate 的元件(如 `Form`、`Field`)及組合函式 (composables)。可以在終端機輸入以下指令來安裝 :
```bash
npm install @vee-validate/nuxt
```
### 安裝注意事項
`@vee-validate/nuxt` 在 Nuxt 最新的 3.13.2 版本會遇到無法自動匯入元件的問題,發生 `The requested module '/_nuxt/node_modules/vee-validate/dist/vee-validate.js' does not provide an export named 'Field’` 錯誤,如下圖。

根據 [GitHub Issue](https://github.com/logaretm/vee-validate/issues/4862) 的討論,此問題不會在 Nuxt 3.12.4 版本發生。建議執行以下步驟將 Nuxt 版本調整至 3.12.4 :
1. 修改 `package.json` dependencies ,將 `nuxt` 版本限定為 3.12.4:
```bash
{
... 其他設定
"dependencies": {
"nuxt": "3.12.4",
},
... 其他設定
}
```
2. 刪除 `package-lock.json` 和 `node_modules` 資料夾並執行 `npm install` 重新安裝。
3. 安裝完成後,檢查 `package-lock.json` 確認 Nuxt 版本是否為 3.12.4 ( 如下圖 ) 。

### 安裝 @vee-validate/rules 與 @vee-validate/i18n
為了使用驗證規則與多國語系功能,還需安裝 `@vee-validate/rules` 和 `@vee-validate/i18n`:
```bash
npm install @vee-validate/rules @vee-validate/i18n
```
## 設定 @vee-validate/nuxt
安裝後,需要在 `nuxt.config.ts` 將 `@vee-validate/nuxt` 模組加入到 `modules` 屬性,啟用 VeeValidate 的元件和功能。
```tsx
// nuxt.config.ts
export default defineNuxtConfig({
// ... 其他設定
modules: ["@vee-validate/nuxt"],
});
```
### 自訂元件名稱
VeeValidate 會自動匯入預設的 `Form`、`Field` 和 `ErrorMessage` 元件。若希望修改這些元件的名稱可以在 `nuxt.config.ts` 中加入 `veeValidate.componentNames` 屬性。例如下方的調整,分別將 `Form`、`Field` 和`ErrorMessage` 的名稱修改為 `VForm`、`VField` 和`VErrorMessage` 。
```jsx
// nuxt.config.ts
export default defineNuxtConfig({
// ... 其他設定
modules: ["@vee-validate/nuxt"],
veeValidate: {
// 修改 VeeValidate 元件的名稱
componentNames: {
Form: "VForm",
Field: "VField",
ErrorMessage: "VErrorMessage",
},
},
})
```
### 關閉自動匯入
若不需要自動匯入 VeeValidate 的元件和組合函式 (composables),可以將 `autoImports` 設為 `false`:
```jsx
// nuxt.config.ts
export default defineNuxtConfig({
// ... 其他設定
modules: ["@vee-validate/nuxt"],
veeValidate: {
// autoImports 預設為 true ( 開啟自動匯入)
// 調整為 false 會關閉自動匯入。
autoImports: false,
},
})
```
## 全域引入驗證規則與多國語系
`@vee-validate/nuxt` 模組不包含驗證規則與多國語系功能,需要透過 Nuxt 的插件系統來全域導入這些功能。以下將逐步說明在 Nuxt3 中全域設定 **VeeValidate** 的驗證規則與多國語系。
### 步驟一. 建立插件
使用 `npx nuxi add plugin vee-validate.js` 指令或手動建立 `plugins/vee-validate.js` 檔案,並將 `defineNuxtPlugin()` 預設匯出。
```jsx
// plugins/vee-validate.js
export default defineNuxtPlugin((nuxtApp) => {
// 插件的設定
});
```
### 步驟二.全域匯入驗證規則
從 [@vee-validate/rules](https://vee-validate.logaretm.com/v4/guide/global-validators#vee-validaterules) 匯入驗證規則,並使用 `defineRule` 定義全域規則。例如使用 `defineRule` 匯入 `required` 規則 :
```jsx
// plugins/vee-validate.js
import { defineRule } from "vee-validate";
import { required } from "@vee-validate/rules";
export default defineNuxtPlugin((nuxtApp) => {
// 定義全域的規則
defineRule("required", required);
});
```
若需要自訂驗證規則,同樣是在插件使用 `defineRule` 定義。例如自訂 `username` 規則驗證使用者名稱,要求 3 至 15 個字元,且只能包含英文字母、數字與底線 :
```jsx
// plugins/vee-validate.js
import { defineRule } from "vee-validate";
import { required } from "@vee-validate/rules";
export default defineNuxtPlugin((nuxtApp) => {
// 定義全域的規則
defineRule("required", required);=
// 自訂驗證規則
defineRule("username", (value) => {
const regex = /^[a-zA-Z0-9_]{3,15}$/;
return (
regex.test(value) ||
"使用者名稱只能包含字母、數字與底線,且長度須為 3 至 15 字元"
);
});
});
```
### 步驟三. 設定多國語系 (i18n)
從 [@vee-validate/i18n](https://vee-validate.logaretm.com/v4/guide/i18n#using-vee-validatei18n) 匯入 `localize` 和 `setLocale` 函式,並設定繁體中文的驗證訊息 :
```jsx
// plugins/vee-validate.js
import { defineRule, configure } from "vee-validate";
import { required } from "@vee-validate/rules";
import { localize, setLocale } from "@vee-validate/i18n";
import zhTW from "@vee-validate/i18n/dist/locale/zh_TW.json";
export default defineNuxtPlugin((nuxtApp) => {
// ...省略驗證規則的設定
// 設定多國語系與驗證訊息
configure({
// 載入繁體中文的設定檔,產生繁體中文的驗證訊息
generateMessage: localize({ zh_TW: zhTW }),
validateOnInput: true, // 輸入文字時立即進行驗證
});
// 設定預設語言為繁體中文
setLocale("zh_TW");
});
```
## 使用 **VeeValidate 元件在 Nuxt 進行表單驗證**
完成 VeeValidate 的安裝與設定後,我們可以開始在 Nuxt 中實作表單驗證。以下將逐步說明如何使用 VeeValidate 元件,設定驗證規則並處理表單提交流程。
### 步驟一. 使用 VForm 元件
在表單的最外層使用 [VForm 元件](https://vee-validate.logaretm.com/v4/api/form#form-component),預設會渲染成 `<form>` 元素。透過 [v-slot](https://vee-validate.logaretm.com/v4/api/form#slots) 插槽,VForm 元件可以獲取表單的驗證結果與操作方法,例如:
- `errors` : 取得表單各欄位的驗證錯誤訊息。
- `meta.valid`:檢查所有欄位的驗證狀態。當所有欄位驗證通過,該值為 `true`;否則為 `false`。可以用來控制送出按鈕的啟用狀態。
- `resetForm`: 用來重置表單狀態的方法。
```html
<VForm v-slot="{ errors, meta, resetForm }">
<!-- 其他表單元素 -->
<!-- 使用 v-slot 取出的 resetForm 方法重置表單 -->
<button class="btn btn-outline-warning" type="button" @click="resetForm">
重新選擇
</button>
<!-- :disabled 屬性綁定從 v-slot 取出的 meta.valid ,檢查欄位的驗證是否有錯誤 -->
<button class="btn btn-primary" type="submit" :disabled="!meta.valid">
送出
</button>
</VForm>
```
> 💡 補充 : 重置表單狀態另一種作法可以在 @submit 事件函式的第二個參數取出 resetForm :
```html
<script setup>
const onSubmit = (value, { resetForm }) => {
// ...執行其他程式
// 可以從第二個參數取出 resetForm 方法來重置表單
resetForm();
};
</script>
<VForm v-slot="{ errors, meta, resetForm } @submit="onSubmit">
<!-- 其他表單元素 -->
</VForm>
```
### 步驟二. 使用 VField 與 VErrorMessage 元件
在 VForm 元件內使用 [VField](https://vee-validate.logaretm.com/v4/api/field#rendering-simple-fields-with-as-prop) [](https://vee-validate.logaretm.com/v4/guide/components/validation#using-errormessage-component)元件來渲染表單的輸入欄位,預設會渲染成 `<input>` 元素。經常使用的屬性包括:
- `name`:定義欄位名稱,用於驗證訊息的對應。例如下方的使用者名稱欄位, `name="username"`
定義了名稱為 `username` 的欄位,驗證失敗會將錯誤訊息存入 `errors.username` 。
- `rules`:指定欄位的驗證規則,規則之間使用 `|` 分隔。例如下方使用者名稱欄位的 `rules="required|username"` 使用了在 `plugins/vee-validate.js` 中定義的 `required` 和 `username` 規則。
- `:class` : 可以根據欄位的驗證錯誤狀態動態套用樣式,例如 `:class="{ 'is-invalid': errors['username'] }"` 會在 `username` 欄位驗證失敗時添加 `is-invalid` 樣式。
```html
<VForm v-slot="{ errors }">
<label class="form-label" for="username">使用者名稱</label>
<VField
id="username"
name="username" <-- 欄位的名稱為 username ,驗證失敗時會對應到此名稱,把錯誤訊息寫入 errors.username
class="form-control"
:class="{ 'is-invalid': errors['username'] }" <-- 將錯誤訊息取出並套用樣式
type="text"
rules="required|username" <--使用全域的 required 和 username 規則
/>
</VForm>
```
- `as` : 改變 VField 元件要渲染的元素。例如使用 `as="textarea"` 可以將 `VField` 渲染成 `<textarea>` 元素,適用於多行文字的輸入區域:
```html
<VForm v-slot="{ errors }">
<label class="form-label" for="userDescription">使用者簡介</label>
<VField
id="userDescription"
name="userDescription"
class="form-control"
:class="{ 'is-invalid': errors['userDescription'] }"
placeholder="請輸入您的簡短自我介紹"
rules="required"
as="textarea" <-- 指定渲染成 <textarea> 元素
/>
</VForm>
```
除此之外,為了顯示驗證錯誤訊息,可以在 VForm 元件內使用 [VErrorMessage](https://vee-validate.logaretm.com/v4/guide/components/validation#using-errormessage-component) 元件,預設會渲染成 `<span>`元素。必需設定的屬性包括 :
- `name` : name 屬性的值必需與對應 `VField` 元件的 `name` 屬性一致,才能正確顯示該欄位的錯誤訊息。
```html
<VForm v-slot="{ errors }">
<div class="mb-3">
<label class="form-label" for="username">使用者名稱</label>
<VField
id="username"
name="username"
class="form-control"
:class="{ 'is-invalid': errors['username'] }"
type="text"
rules="required|username"
/>
<!-- name 屬性的值必需與 VField 元件 name 屬性的值 ( username ) 一致 -->
<VErrorMessage class="invalid-feedback" name="username" />
</div>
<div class="mb-3">
<label class="form-label" for="userDescription">使用者簡介</label>
<VField
id="userDescription"
name="userDescription"
class="form-control"
:class="{ 'is-invalid': errors['userDescription'] }"
placeholder="請輸入您的簡短自我介紹"
rules="required"
as="textarea"
/>
<!-- name 屬性的值必需與 VField 元件 name 屬性的值 ( userDescription ) 一致 -->
<VErrorMessage class="invalid-feedback" name="userDescription" />
</div>
</VForm>
```
### 步驟三. 提交表單
最後,可以在 VForm 元件使用 `@submit` 事件處理提交邏輯。因為 VForm 元件觸發 `@submit` 事件會自動調用 `event.preventDefault()`,所以不需要額外使用 `.prevent` 修飾符。除此之外,表單提交時會自動將驗證後的資料傳遞給事件函式,不需要在每個 VField 欄位綁定 `v-model` ,例如:
```html
<script setup>
const onSubmit = (value, { resetForm }) => {
console.log("表單提交的值", value);
// 可以從第二個參數取出 resetForm 方法來重置表單
resetForm();
};
</script>
<template>
<!-- 1. @submit 事件不需加入 .prevent 修飾符 -->
<VForm v-slot="{ errors, meta, resetForm }" @submit="onSubmit">
<!-- 2. 不需要在 VField 綁定 v-model,表單提交時會自動將值傳遞給 submit 函式 -->
<VField
id="username"
name="username"
class="form-control"
:class="{ 'is-invalid': errors['username'] }"
type="text"
rules="required|username"
/>
<VErrorMessage class="invalid-feedback" name="username" />
<button class="btn btn-outline-warning" type="button" @click="resetForm">
重置
</button>
<button class="btn btn-primary" type="submit" :disabled="!meta.valid">
送出
</button>
</VForm>
</template>
```
<br>
> 今日學習的[範例 Code - 資料夾: day21-vee-validate-example](https://github.com/hexschool/nuxt-daily-tasks-2024)
## 題目
請 fork 這一份 [模板](https://github.com/jasonlu0525/nuxt3-live-question/tree/day21-vee-validate),完成以下條件 :
- 將 `/pages/index.vue` 中的表單改成使用 VeeValidate 驗證。
- 所有欄位必需進行驗證,要求如下 :
1. 所有欄位都必需填寫。
2. 姓名欄位需要填寫至少 2 個字元。
3. 手機號碼欄位需要符合下方正規表達式的格式 :
```html
/^(09)[0-9]{8}$/
```
- 欄位驗證失敗時套用 Bootstrap 5 的 `is-invalid` 樣式。
- 使用 VeeValidate 的元件顯示驗證失敗的訊息。
- 透過 submit 事件處理表單提交。提交後使用 VeeValidate 的 resetForm 方法將表單重置。
- 模板已有安裝 VeeValidate 表單驗證所需套件,將 `@vee-validate/nuxt` 、驗證規則以及多國語系整合至 Nuxt3。以下是套件在使用上的細節 :
- 表單驗證元件的名稱沒有限制,可以使用預設的元件 ( 如 `<Form>` ) 或是自訂元件名稱。
- 不需載入所有驗證規則,只需載入表單所需的規則即可。
- 多國語系的語言需使用繁體中文 ( zhTW )。
## 回報流程
將答案上傳至 GitHub 並複製 GitHub repo 連結貼至底下回報就算完成了喔 !
解答位置請參考下圖(需打開程式碼的部分觀看)

<!--
解答 : https://github.com/jasonlu0525/nuxt3-live-answer/tree/day21-vee-validate
-->
回報區
---
| # | Discord | Github / 答案 |
| --- | ----- | ----- |
| 1 | Steven |[Github](https://github.com/y7516552/nuxt3-live-question/tree/day21)|
| 2 | dragon |[Github](https://github.com/peterlife0617/2024-nuxt-training-homework01/tree/feature/day21)|
| 3 | 眼睛 |[Github](https://github.com/Thrizzacode/nuxt3-live-question/tree/day21-vee-validate)|
| 4 | LinaChen |[Github](https://github.com/Lina-SHU/nuxt3-live-question)|
| 5 | Johnson |[Github](https://github.com/tttom3669/2024_hex_nuxt_daily/tree/day21-vee-validate)|
| 6 | Rocky |[Github](https://github.com/WuRocky/Nuxt-Day21-VeeValidate)|
| 7 | tanuki狸 |[Github](https://github.com/tanukili/Nuxt-2024-week01-2/tree/day21-vee-validate)|
|8|hsin yu|[Github](https://github.com/dogwantfly/nuxt3-daily-task-live-question/tree/day21-vee-validate)|
<!--
|---|---|[Github]()|
-->