如何實作表單驗證
本指南說明如何使用框架內建的 schema 驗證器搭配 react-hook-form 實作表單驗證。
前置條件
react-hook-form 已包含在 @appfuse/appfuse-web 的 peer dependencies 中,框架內建的驗證器無需額外安裝。
禁止使用 Zod
AppFuse Web 提供輕量級驗證器(~5KB),禁止使用 Zod(~60KB)。前端僅驗證類型和必填,格式驗證由後端處理。
基本驗證
步驟 1:定義 Schema
import { schema } from '@appfuse/appfuse-web/form/validator';
const customerSchema = schema.object({
name: schema.string().required(),
email: schema.string().required(),
phone: schema.string().required(),
});
步驟 2:建立表單
import { useForm } from 'react-hook-form';
import { validatorResolver } from '@appfuse/appfuse-web/form/validator';
import { Input } from '@appfuse/appfuse-web/form';
function CustomerForm() {
const { control, handleSubmit } = useForm({
resolver: validatorResolver(customerSchema),
defaultValues: {
name: '',
email: '',
phone: '',
},
});
const onSubmit = (data) => {
console.log('Valid data:', data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Input name="name" control={control} label="Name" />
<Input name="email" control={control} label="Email" type="email" />
<Input name="phone" control={control} label="Phone" />
<button type="submit" className="btn btn-primary">Save</button>
</form>
);
}
常見驗證規則
必填欄位
const mySchema = schema.object({
// 字串必填
name: schema.string().required(),
// 選擇欄位必填
category: schema.string().required(),
// 數字必填
price: schema.number().required(),
});
選填欄位(nullable)
const mySchema = schema.object({
// 選填字串
note: schema.string(),
// 選填數字
discount: schema.number().nullable(),
// 選填陣列
tags: schema.array(),
});
預設值設計
// ✅ 正確的預設值
const defaultValues = {
name: '', // 字串欄位用空字串
category: null, // 選擇欄位用 null
price: null, // 數字欄位用 null
date: null, // 日期欄位用 null
};
// ❌ 錯誤
const defaultValues = {
category: '', // Select 不應使用空字串
price: 0, // 0 是有意義的值,不是空值
};
後端違例處理
後端回傳的驗證錯誤(violations)可自動映射到表單欄位:
import { createServerErrorHandler } from '@appfuse/appfuse-web/form/validator';
import { prompt } from '@appfuse/appfuse-web/messaging';
function CustomerForm() {
const { control, handleSubmit, setError } = useForm({
resolver: validatorResolver(customerSchema),
});
const onSubmit = async (data) => {
try {
await customerService.create(data);
prompt.success('Customer created');
} catch (error) {
// 自動將後端 violations 映射到表單欄位錯誤
const handler = createServerErrorHandler(setError);
handler(error);
}
};
return <form onSubmit={handleSubmit(onSubmit)}>...</form>;
}
錯誤訊息翻譯
錯誤訊息會自動翻譯,${field} 會被替換為欄位名稱:
// nls/message/zh-TW.ts
export default {
'${field} is required': '${field}為必填',
};
驗證策略
AppFuse Web 採用前後端分工的驗證策略:
| 驗證類型 | 前端 | 後端 |
|---|---|---|
| 類型檢查 | ✅ schema.string() | ✅ |
| 必填 | ✅ .required() | ✅ @NotBlank |
| 長度/格式 | ❌ | ✅ @Length, @Email |
| 唯一性 | ❌ | ✅ Database |
| 跨欄位驗證 | ❌ | ✅ Custom Validator |