iT邦幫忙

2024 iThome 鐵人賽

DAY 9
1
Modern Web

Vue 和 TypeScript 的最佳實踐:成為前端工程師的進階利器系列 第 9

Day 9: 高階組件設計:使用 Zod 和 Vee-Validate 進行動態表單驗證

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20240920/20117461E8C7F4zfaV.jpg

介紹

在 Vue 應用開發中,表單驗證是一項至關重要的功能,尤其是當表單數據變得複雜且需要高度自定義時。Zod 作為一個強大的 JavaScript 驗證庫,可以與 Vee-Validate 無縫結合,提供靈活且強大的運行時驗證能力。本文將介紹如何使用 Zod 的 refinesuperRefinesafeParsesafeParseAsync,結合 Vee-Validate 和 @vee-validate/zod,實現 email、IP、台灣電話號碼等複雜驗證,並進行異步驗證操作。

zod中階表單操作

步驟1. 定義 Zod Schema 並使用 refine 和 superRefine

Zod 提供了多種高階驗證方法,其中 refinesuperRefine 可以幫助我們在 Schema 中實現自定義驗證邏輯。以下是定義表單驗證的 Zod schema,包含 email、IP、url、地址、台灣電話號碼,以及使用 fetch API 進行的異步驗證。

(檔案: src/schemas/userFormSchema.ts)

import * as zod from 'zod';
import { useDebounceFn } from '@vueuse/core'; // 後續會介紹講到 vueuse 的部分

export const userFormSchema = zod
  .object({
    email: zod.string().email('請輸入有效的電子郵件地址'),
    imageUrl: zod.string().url('請輸入有效的圖片 url'),
    ip: zod.string().ip('請輸入有效的 ip 位置'),
    taiwanPhone: zod.string().refine((value) => {
      const taiwanPhoneRegex = /^09\d{8}$/;
      return taiwanPhoneRegex.test(value);
    }, '請輸入有效的台灣電話號碼'),
    token: zod.string().refine(useDebounceFn(async (value) => {
      // 模擬 API 驗證
      const response = await fetch(`https://api.example.com/validate?token=${value}`);
      const data = await response.json();
      return data.isValid;
    }, '驗證失敗,請輸入有效 token'),
    password: zod.string(),
    confirmPassword: zod.string()
  }, 800))
  .superRefine(({ password, confirmPassword }, ctx) => {
    if (password === confirmPassword) {
      ctx.addIssue({
        code: zod.ZodIssueCode.custom,
        message: '確認密碼和密碼不符',
        path: ['password', 'confirmPassword'],
      });
    }
  });
  
export type UserFormSchema = zod.infer<typeof userFormSchema>;

這個 schema 使用了 refine 來驗證台灣電話號碼,以及一個需要異步驗證的 token
superRefine 用於更複雜的驗證邏輯,例如密碼確認密碼必須一致。

步驟2. 使用 Vee-Validate 和 Zod 在 Vue 表單中進行驗證

接下來,我們將使用 useFormuseField 在 Vue 中進行表單驗證,並將 Zod schema 結合到 Vee-Validate。

(檔案: src/components/userFormComponent.vue)

<script lang="ts" setup>
  import { useUserForm } from '../composables/useUserForm';

  const {
    // state::
    errors,
    // field::
    email,
    imageUrl,
    ip,
    taiwanPhone,
    token,
    password,
    confirmPassword,
    // methods::
    submitForm
  } = useUserForm();

</script>

<template>
  <form @submit.prevent="submitForm">
    <div>
      <label for="email">電子郵件</label>
      <input id="email" v-model="email" />
      <span v-if="errors.email">{{ errors.email }}</span>
    </div>

    <div>
      <label for="imageUrl">圖片網址</label>
      <input id="imageUrl" v-model="imageUrl" />
      <span v-if="errors.imageUrl">{{ errors.imageUrl }}</span>
    </div>


    <div>
      <label for="ip">IP 地址</label>
      <input id="ip" v-model="ip" />
      <span v-if="errors.ip">{{ errors.ip }}</span>
    </div>

    <div>
      <label for="taiwanPhone">台灣電話號碼</label>
      <input id="taiwanPhone" v-model="taiwanPhone" />
      <span v-if="errors.taiwanPhone">{{ errors.taiwanPhone }}</span>
    </div>

    <div>
      <label for="token">驗證信 token</label>
      <input id="token" v-model="token" />
      <span v-if="errors.token">{{ errors.token }}</span>
    </div>

    <div>
      <label for="password">密碼</label>
      <input id="password" v-model="password" />
      <span v-if="errors.password">{{ errors.password }}</span>
    </div>

    <div>
      <label for="confirmPassword">確認密碼</label>
      <input id="confirmPassword" v-model="confirmPassword" />
      <span v-if="errors.confirmPassword">{{ errors.confirmPassword }}</span>
    </div>

    <button type="submit">提交</button>
  </form>
</template>

(檔案: src/composables/useUserForm.ts)

import { useForm, useField } from 'vee-validate';
import { toTypedSchema } from '@vee-validate/zod';
import { userFormSchema, type UserFormSchema } from '../schemas/userFormSchema';

export const useUserForm = () => {
  const validationSchema = toTypedSchema(userFormSchema);
  
  const initialValues: UserFormSchema = {
    email: '',
    imageUrl: '',
    ip: '',
    taiwanPhone: '',
    token: '',
    password: '',
    confirmPassword: '',
  };

  // 設置 Vee-Validate 的 useForm 並綁定 Zod schema
  const { handleSubmit, errors } = useForm({
    validationSchema,
    initialValues
  });

  // 使用 useField 將每個字段的驗證邏輯和 UI 結合
  const email = useField<string>('email');
  const imageUrl = useField<string>('imageUrl');
  const ip = useField<string>('ip');
  const taiwanPhone = useField<string>('taiwanPhone');
  const token = useField<string>('asyncValue');
  const password = useField<string>('password');
  const confirmPassword = useField<string>('confirmPassword');

  const submitForm = handleSubmit((values) => {
    console.log('表單驗證成功:', values);
  });

  return {
    // state::
    errors,
    // field::
    email,
    imageUrl,
    ip,
    taiwanPhone,
    token,
    password,
    confirmPassword,
    // methods::
    submitForm
  };
};

export type UseUserForm = typeof useUserForm;

步驟3. 使用 safeParse 和 safeParseAsync 進行手動驗證

Zod 的 safeParsesafeParseAsync 讓我們能夠在提交表單前或其他情況下手動驗證數據,這樣可以靈活處理表單提交的驗證流程。

// 手動驗證數據的範例
import { formSchema } from '../schemas/userFormSchema';

// 同步驗證數據,因為 refine內有非同步動作,所以建議用 safeParseAsync 進行驗證
// 這裡僅是示範
const result = userFormSchema.safeParse({
  email: 'user@example.com',
  imageUrl: 'http://www.exampleImage.com/test.jpg',
  ip: '192.168.0.1',
  taiwanPhone: '0912345678',
  token: 'someValue',
  password: 'hello password',
  confirmPassword: 'hello password',
});

if (!result.success) {
  console.error('同步驗證失敗:', result.error.errors);
} else {
  console.log('同步驗證成功:', result.data);
}

// 異步驗證數據
const asyncResult = await userFormSchema.safeParseAsync({
  email: 'user@example.com',
  imageUrl: 'http://www.exampleImage.com/test.jpg',
  ip: '192.168.0.1',
  taiwanPhone: '0912345678',
  token: 'someValue',
  password: 'hello password',
  confirmPassword: 'hello password',
});

if (!asyncResult.success) {
  console.error('異步驗證失敗:', asyncResult.error.errors);
} else {
  console.log('異步驗證成功:', asyncResult.data);
}

結論

結合 Zod 的強大驗證功能和 Vee-Validate 的靈活性,我們可以實現複雜且高效的動態表單驗證。通過 refinesuperRefine,可以實現自定義驗證邏輯,而 safeParsesafeParseAsync 則提供了更多控制驗證流程的方式。這些功能使得我們在處理高階組件設計時能夠更加從容和靈活。

希望這篇文章能幫助你在 Vue 項目中更好地應用 Zod 和 Vee-Validate 進行動態表單驗證!


上一篇
Day 8: 使用 Pinia 實現 Vue 中的複雜狀態管理
下一篇
Day 10: 使用 Vue Router 實現基於角色的路由權限控制
系列文
Vue 和 TypeScript 的最佳實踐:成為前端工程師的進階利器30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言