本頁介紹 @tanstack/react-form 函式庫中使用的基本概念與術語。熟悉這些概念將幫助您更有效地理解並運用此函式庫。
您可以使用 formOptions 函式建立表單選項,以便在多個表單間共享設定。
範例:
interface User {
firstName: string
lastName: string
hobbies: Array<string>
}
const defaultUser: User = { firstName: '', lastName: '', hobbies: [] }
const formOpts = formOptions({
defaultValues: defaultUser,
})
interface User {
firstName: string
lastName: string
hobbies: Array<string>
}
const defaultUser: User = { firstName: '', lastName: '', hobbies: [] }
const formOpts = formOptions({
defaultValues: defaultUser,
})
表單實例是一個代表單一表單的物件,提供操作表單的方法與屬性。您可透過表單選項提供的 useForm 鉤子來建立表單實例。此鉤子接受包含 onSubmit 函式的物件,該函式會在表單提交時被呼叫。
const form = useForm({
...formOpts,
onSubmit: async ({ value }) => {
// 處理表單資料
console.log(value)
},
})
const form = useForm({
...formOpts,
onSubmit: async ({ value }) => {
// 處理表單資料
console.log(value)
},
})
您也可以不使用 formOptions,直接透過獨立的 useForm API 建立表單實例:
interface User {
firstName: string
lastName: string
hobbies: Array<string>
}
const defaultUser: User = { firstName: '', lastName: '', hobbies: [] }
const form = useForm({
defaultValues: defaultUser,
onSubmit: async ({ value }) => {
// 處理表單資料
console.log(value)
},
})
interface User {
firstName: string
lastName: string
hobbies: Array<string>
}
const defaultUser: User = { firstName: '', lastName: '', hobbies: [] }
const form = useForm({
defaultValues: defaultUser,
onSubmit: async ({ value }) => {
// 處理表單資料
console.log(value)
},
})
欄位代表單一表單輸入元素,例如文字輸入框或核取方塊。欄位是透過表單實例提供的 form.Field 元件來建立。此元件接受 name 屬性,需與表單預設值中的鍵名匹配,以及 children 屬性(這是一個接收欄位物件作為參數的渲染函式)。
範例:
<form.Field
name="firstName"
children={(field) => (
<>
<input
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
<FieldInfo field={field} />
</>
)}
/>
<form.Field
name="firstName"
children={(field) => (
<>
<input
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
<FieldInfo field={field} />
</>
)}
/>
每個欄位都有自身的狀態,包含當前值、驗證狀態、錯誤訊息及其他元資料。您可透過 field.state 屬性存取欄位狀態。
範例:
const {
value,
meta: { errors, isValidating },
} = field.state
const {
value,
meta: { errors, isValidating },
} = field.state
有三種欄位狀態有助於觀察使用者互動:當使用者點擊/切換至欄位時為「已觸碰 (touched)」、使用者未改變值前為「原始狀態 (pristine)」、值變更後則為「已修改 (dirty)」。可透過以下標誌檢查這些狀態:
const { isTouched, isPristine, isDirty } = field.state.meta
const { isTouched, isPristine, isDirty } = field.state.meta
給從 React Hook Form 轉換的使用者重要提示:TanStack/form 中的 isDirty 標誌與 RHF 中同名的標誌意義不同。 在 RHF 中,當表單值與原始值不同時 isDirty = true。若使用者修改表單值後又改回與預設值相同,RHF 的 isDirty 會是 false,但在 TanStack/form 中會是 true。 TanStack/form 同時在表單與欄位層級暴露預設值 (form.options.defaultValues, field.options.defaultValue),因此若需模擬 RHF 的行為,您可以自行編寫 isDefaultValue() 輔助函式。
欄位 API 是建立欄位時傳遞給渲染函式的物件,提供操作欄位狀態的方法。
範例:
<input
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
<input
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
@tanstack/react-form 原生支援同步與非同步驗證。驗證函式可透過 validators 屬性傳遞給 form.Field 元件。
範例:
<form.Field
name="firstName"
validators={{
onChange: ({ value }) =>
!value
? '必須填寫名字'
: value.length < 3
? '名字至少需 3 個字元'
: undefined,
onChangeAsync: async ({ value }) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return value.includes('error') && '名字中不得包含 "error"'
},
}}
children={(field) => (
<>
<input
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
<FieldInfo field={field} />
</>
)}
/>
<form.Field
name="firstName"
validators={{
onChange: ({ value }) =>
!value
? '必須填寫名字'
: value.length < 3
? '名字至少需 3 個字元'
: undefined,
onChangeAsync: async ({ value }) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return value.includes('error') && '名字中不得包含 "error"'
},
}}
children={(field) => (
<>
<input
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
<FieldInfo field={field} />
</>
)}
/>
除了手動驗證選項外,我們也支援 Standard Schema 規範。
您可以使用任何實作此規範的函式庫定義結構描述,並將其傳遞給表單或欄位驗證器。
支援的函式庫包括:
import { z } from 'zod'
const userSchema = z.object({
age: z.number().gte(13, '必須年滿 13 歲才能建立帳戶'),
})
function App() {
const form = useForm({
defaultValues: {
age: 0,
},
validators: {
onChange: userSchema,
},
})
return (
<div>
<form.Field
name="age"
children={(field) => {
return <>{/* ... */}</>
}}
/>
</div>
)
}
import { z } from 'zod'
const userSchema = z.object({
age: z.number().gte(13, '必須年滿 13 歲才能建立帳戶'),
})
function App() {
const form = useForm({
defaultValues: {
age: 0,
},
validators: {
onChange: userSchema,
},
})
return (
<div>
<form.Field
name="age"
children={(field) => {
return <>{/* ... */}</>
}}
/>
</div>
)
}
@tanstack/react-form 提供多種訂閱表單與欄位狀態變更的方式,最顯著的是 useStore(form.store) 鉤子與 form.Subscribe 元件。這些方法讓您能透過僅在必要時更新元件來優化表單渲染效能。
範例:
const firstName = useStore(form.store, (state) => state.values.firstName)
//...
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
children={([canSubmit, isSubmitting]) => (
<button type="submit" disabled={!canSubmit}>
{isSubmitting ? '...' : '提交'}
</button>
)}
/>
const firstName = useStore(form.store, (state) => state.values.firstName)
//...
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
children={([canSubmit, isSubmitting]) => (
<button type="submit" disabled={!canSubmit}>
{isSubmitting ? '...' : '提交'}
</button>
)}
/>
需特別注意,雖然 useStore 鉤子的 selector 屬性是可選的,但強烈建議提供此參數,因為省略它會導致不必要的重新渲染。
// 正確用法
const firstName = useStore(form.store, (state) => state.values.firstName)
const errors = useStore(form.store, (state) => state.errorMap)
// 錯誤用法
const store = useStore(form.store)
// 正確用法
const firstName = useStore(form.store, (state) => state.values.firstName)
const errors = useStore(form.store, (state) => state.errorMap)
// 錯誤用法
const store = useStore(form.store)
注意:不建議使用 useField 鉤子來實現響應式,因為它設計上是供 form.Field 元件內部謹慎使用的。您應考慮改用 useStore(form.store)。
@tanstack/react-form 允許您對特定觸發事件作出反應並「監聽」它們以執行副作用。
範例:
<form.Field
name="country"
listeners={{
onChange: ({ value }) => {
console.log(`國家已變更為: ${value}, 重置省份選項`)
form.setFieldValue('province', '')
},
}}
/>
<form.Field
name="country"
listeners={{
onChange: ({ value }) => {
console.log(`國家已變更為: ${value}, 重置省份選項`)
form.setFieldValue('province', '')
},
}}
/>
更多資訊請參閱 監聽器
陣列欄位讓您能管理表單中的值列表,例如興趣愛好清單。您可透過 form.Field 元件加上 mode="array" 屬性來建立陣列欄位。
操作陣列欄位時,可使用欄位的 pushValue、removeValue、swapValues 和 moveValue 方法來新增、移除與交換陣列中的值。
範例:
<form.Field
name="hobbies"
mode="array"
children={(hobbiesField) => (
<div>
興趣愛好
<div>
{!hobbiesField.state.value.length
? '尚未新增任何興趣愛好。'
: hobbiesField.state.value.map((_, i) => (
<div key={i}>
<form.Field
name={`hobbies[${i}].name`}
children={(field) => {
return (
<div>
<label htmlFor={field.name}>名稱:</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
<button
type="button"
onClick={() => hobbiesField.removeValue(i)}
>
X
</button>
<FieldInfo field={field} />
</div>
)
}}
/>
<form.Field
name={`hobbies[${i}].description`}
children={(field) => {
return (
<div>
<label htmlFor={field.name}>描述:</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
<FieldInfo field={field} />
</div>
)
}}
/>
</div>
))}
</div>
<button
type="button"
onClick={() =>
hobbiesField.pushValue({
name: '',
description: '',
yearsOfExperience: 0,
})
}
>
新增興趣愛好
</button>
</div>
)}
/>
<form.Field
name="hobbies"
mode="array"
children={(hobbiesField) => (
<div>
興趣愛好
<div>
{!hobbiesField.state.value.length
? '尚未新增任何興趣愛好。'
: hobbiesField.state.value.map((_, i) => (
<div key={i}>
<form.Field
name={`hobbies[${i}].name`}
children={(field) => {
return (
<div>
<label htmlFor={field.name}>名稱:</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
<button
type="button"
onClick={() => hobbiesField.removeValue(i)}
>
X
</button>
<FieldInfo field={field} />
</div>
)
}}
/>
<form.Field
name={`hobbies[${i}].description`}
children={(field) => {
return (
<div>
<label htmlFor={field.name}>描述:</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
<FieldInfo field={field} />
</div>
)
}}
/>
</div>
))}
</div>
<button
type="button"
onClick={() =>
hobbiesField.pushValue({
name: '',
description: '',
yearsOfExperience: 0,
})
}
>
新增興趣愛好
</button>
</div>
)}
/>
當結合使用 <button type="reset"> 與 TanStack Form 的 form.reset() 時,需阻止預設的 HTML 重設行為,以避免表單元素(特別是 <select> 元素)意外重設至初始 HTML 值。在按鈕的 onClick 處理程序中使用 event.preventDefault() 來阻止原生表單重設。
範例:
<button
type="reset"
onClick={(event) => {
event.preventDefault()
form.reset()
}}
>
重設
</button>
<button
type="reset"
onClick={(event) => {
event.preventDefault()
form.reset()
}}
>
重設
</button>
或者,您可以使用 <button type="button"> 來避免原生 HTML 重設行為。
<button
type="button"
onClick={() => {
form.reset()
}}
>
重設
</button>
<button
type="button"
onClick={() => {
form.reset()
}}
>
重設
</button>
以上是 @tanstack/react-form 函式庫中使用的基本概念與術語。理解這些概念將幫助您更有效率地運用此函式庫,輕鬆建立複雜的表單。
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.