React?Hook?Form?優(yōu)雅處理表單使用指南
受控組件與非受控組件
受控組件
先說說受控組件,以 input 為例:
const [value,setValue] = useState('')
<input value={value} onChange={(e)=> setValue(e.target.value)} />
在上面的代碼中,我們通過通過自己維護(hù)一個 state 來獲取或更新 input 輸入的值,以這種方式控制取值的 表單輸入元素 就叫做 受控組件。
非受控組件
那么什么是非受控組件呢?在 React 中,非受控組件是指表單元素的值由 DOM 節(jié)點(diǎn)直接處理,而不是由 React 組件來管理。如下例:
import React, { useRef } from 'react';
function UncontrolledForm() {
const nameInputRef = useRef(null);
const emailInputRef = useRef(null);
const passwordInputRef = useRef(null);
function handleSubmit(event) {
console.log('Name:', nameInputRef.current.value);
console.log('Email:', emailInputRef.current.value);
console.log('Password:', passwordInputRef.current.value);
event.preventDefault();
}
return (
<form onSubmit={handleSubmit}>
<label>
Name:
<input type="text" ref={nameInputRef} />
</label>
<label>
Email:
<input type="email" ref={emailInputRef} />
</label>
<label>
Password:
<input type="password" ref={passwordInputRef} />
</label>
<button type="submit">Submit</button>
</form>
);
}
在這個例子中,我們使用 useRef Hook 創(chuàng)建了一個 ref 對象,并將其賦值給每個 input 元素的 ref 屬性。在 handleSubmit 函數(shù)中,我們使用 ref.current.value 來獲取每個 input 元素的值。這里的每個input 元素都是非受控組件,因?yàn)樗鼈兊闹涤?DOM 節(jié)點(diǎn)直接處理,而不是由 React 組件來管理。
當(dāng)然,這意味著當(dāng)用戶輸入數(shù)據(jù)時,React 無法追蹤表單元素的值。因此,當(dāng)您需要訪問表單元素的值時,您需要使用DOM API來獲取它們。
為什么需要非受控組件
在 React 中,通常使用受控組件來處理表單。受控組件表單元素的值由 React 組件來管理,當(dāng)表單數(shù)據(jù)發(fā)生變化時,React 會自動更新組件狀態(tài),并重新渲染組件。這種方式可以使得表單處理更加可靠和方便,也可以使得表單數(shù)據(jù)和應(yīng)用狀態(tài)之間保持一致。
但在實(shí)際的開發(fā)中,表單往往是最復(fù)雜的場景,有的表單有數(shù)十個字段,如果使用受控組件去構(gòu)建表單,那么我們就需要維護(hù)大量 state,且 React 又不像 Vue 可以通過雙向綁定直接修改 state 的值,每一個表單字段還需要定義一下 onChange 方法。因此在維護(hù)復(fù)雜表單時,使用受控組件會有很大的額外代碼量。
為了解決受控組件帶來的問題,我們可以使用非受控組件來構(gòu)建表單。受控組件主要有以下三個優(yōu)點(diǎn)
- 可以減少組件的 代碼量和復(fù)雜度,因?yàn)榉鞘芸亟M件不需要在組件狀態(tài)中保存表單數(shù)據(jù)。
- 可以更好地 處理大量表單數(shù)據(jù),因?yàn)榉鞘芸亟M件可以讓您直接操作DOM元素,而不需要將所有表單數(shù)據(jù)存儲在組件狀態(tài)中。
- 可以更容易地與第三方 JavaScript 庫和表單處理代碼集成,因?yàn)榉鞘芸亟M件使您能夠使用 DOM API 或 ref 直接訪問表單元素,而不是在 React 中重新實(shí)現(xiàn)所有的表單處理邏輯。
React Hook Form 是什么?
React Hook Form 是一個基于 React 的 輕量級表單驗(yàn)證庫。它使用了 React Hook API,讓表單驗(yàn)證變得簡單、易用、高效。React Hook Form 的主要目標(biāo)是提供一個簡單易用的表單驗(yàn)證解決方案,同時還能保持高性能和低開銷。
React Hook Form 的特點(diǎn)都在官網(wǎng)首頁 react-hook-form.com 中以可交互的形式展示,包括了以下幾點(diǎn):
- 通過 React Hook 的方式減少使用時的代碼量,簡單易上手,并且移除了不必要的重復(fù)渲染:

- 隔離重復(fù)渲染,自組件重新渲染時不會觸發(fā)父組件或兄弟組件的重新渲染:

- 訂閱機(jī)制,與上一點(diǎn)相似,能夠訂閱單個輸入和表單狀態(tài)更新,而無需重新呈現(xiàn)整個表單:

- 組件渲染速度足夠快,而且代碼庫非常小,壓縮后只有幾 KB 大小,不會對頁面性能造成任何影響:

React Hook Form 的使用姿勢
數(shù)據(jù)收集
先看看最基礎(chǔ)的表單實(shí)現(xiàn):
import React from "react";
import { useForm } from "react-hook-form";
function MyForm() {
const { register, handleSubmit } = useForm();
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("firstName")} />
<input {...register("lastName")} />
<button type="submit">Submit</button>
</form>
);
}
咱們來分析一下這段代碼:
- 使用
useForm函數(shù)創(chuàng)建一個表單對象,該函數(shù)返回一個包含register和handleSubmit等方法的對象。 - 在表單中定義兩個輸入框,使用
register函數(shù)注冊表單輸入組件,并指定組件的名稱為firstName和lastName。 - 使用 React Hook Form 提供的
handleSubmit函數(shù)來管理表單的提交和數(shù)據(jù)驗(yàn)證。
這里我們不需要定義任何 state 即可在 submit 時獲取到表單中的數(shù)據(jù),接下來我們補(bǔ)充一下基本的表單驗(yàn)證和錯誤提示:
import React from "react";
import { useForm } from "react-hook-form";
function MyForm() {
const onSubmit = (data) => {
console.log(data);
};
const { register, handleSubmit, formState: { errors } } = useForm();
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("firstName", { required: true })} />
{errors.firstName && <p>First name is required.</p>}
<input {...register("lastName", { required: true })} />
{errors.lastName && <p>Last name is required.</p>}
<button type="submit">Submit</button>
</form>
);
}
咱們再分析一下這段代碼:
register函數(shù)中可以指定表單輸入組件的 驗(yàn)證規(guī)則 ,例如使用required規(guī)則來驗(yàn)證輸入框的必填項(xiàng)。- 在表單提交處理函數(shù)中,可以調(diào)用 React Hook Form 提供的
handleSubmit函數(shù)來自動執(zhí)行表單驗(yàn)證,并返回驗(yàn)證結(jié)果。只有表單驗(yàn)證通過才會執(zhí)行onSubmit方法。 - 如果表單驗(yàn)證失敗,可以使用
errors對象來獲取每個表單輸入組件的驗(yàn)證錯誤信息,并在 UI 上顯示錯誤提示。
register 函數(shù)是用來注冊表單輸入組件的,當(dāng)組件注冊之后,React Hook Form 會自動收集該組件的值,并根據(jù)驗(yàn)證規(guī)則進(jìn)行驗(yàn)證。 register 函數(shù)會返回一個對象,其中包含了一些屬性和方法,例如:
const { ref, onChange, onBlur, name } = register("firstName");
ref 屬性是一個引用,指向該輸入組件的 DOM 元素,onChange 和 onBlur 是回調(diào)函數(shù),用于處理該組件的值變化和失去焦點(diǎn)事件,name 是該組件的名稱。
register 函數(shù)內(nèi)部會創(chuàng)建一個管理表單輸入組件的對象,包含了該組件的名稱、引用、驗(yàn)證規(guī)則等信息。同時,還會將該對象保存在 React Hook Form 內(nèi)部的一個數(shù)據(jù)結(jié)構(gòu)中。
在表單提交時,React Hook Form 會遍歷管理的所有表單輸入組件,并收集它們的值,并根據(jù)其注冊時定義的驗(yàn)證規(guī)則進(jìn)行驗(yàn)證。
到這里,一個最基本的表單驗(yàn)證及提交就已經(jīng)實(shí)現(xiàn)了。當(dāng)然,在實(shí)際開發(fā)中,表單之所以復(fù)雜是由于各種條件渲染及表單嵌套引起的,那么我們接下來再看看使用 React Hook Form 如何處理這些場景。
表單嵌套
還是一樣,先看看示例:
父級表單:
// ParentForm.jsx
import React from "react";
import { useForm, FormProvider } from "react-hook-form";
import ChildForm from "./ChildForm";
function ParentForm() {
const methods = useForm();
const { register, handleSubmit, formState: { errors } } = methods;
const onSubmit = (data) => {
console.log(data);
};
return (
<FormProvider {...methods}>
<form onSubmit={handleSubmit(onSubmit)}>
<h2>Parent Form</h2>
<label htmlFor="firstName">First Name</label>
<input {...register("firstName", { required: true })} />
{errors.firstName && <p>First name is required.</p>}
<label htmlFor="lastName">Last Name</label>
<input {...register("lastName", { required: true })} />
{errors.lastName && <p>Last name is required.</p>}
<ChildForm/>
<button type="submit">Submit</button>
</form>
</FormProvider>
);
}
export default ParentForm;
子級表單:
import React from "react";
import { useFormContext } from "react-hook-form";
function ChildForm() {
const { register, errors } = useFormContext();
return (
<div>
<h2>Child Form</h2>
<label htmlFor="childFirstName">Child First Name</label>
<input {...register("child.firstName", { required: true })} />
{errors.child?.firstName && <p>Child first name is required.</p>}
<label htmlFor="childLastName">Child Last Name</label>
<input {...register("child.lastName", { required: true })} />
{errors.child?.lastName && <p>Child last name is required.</p>}
</div>
);
}
export default ChildForm;
分析一下這兩個組件的代碼:
在 ParentForm 組件中,我們使用 useForm hook 來獲取表單的注冊函數(shù)、表單狀態(tài)等信息,并使用 FormProvider 組件將其傳遞給所有的子組件。
在 ChildForm 組件中,我們使用了 useFormContext hook 來獲取父表單的注冊函數(shù)和表單狀態(tài)。
這里的兩個表單組件間并不需要咱們?nèi)为?dú)定義 props ,只需要將 useFormContext 與 FormProvider 搭配使用,就可以將一個嵌套表單的邏輯分離成多個組件進(jìn)行處理,且可以在父級組件提交時統(tǒng)一獲取并處理數(shù)據(jù)。
FormProvider 是 React Hook Form 提供的一個組件,用于在 React 組件樹中向下傳遞 useForm hook 的實(shí)例。它創(chuàng)建了一個 React Context,并將 useForm hook 的實(shí)例作為 Context 的值,然后通過 Context.Provider 組件將這個值傳遞給所有子組件.
而 useFormContext 則可以在子組件中獲取到 FormProvider 提供的 useForm hook 的返回值。在使用 useFormContext 時,不需要手動使用 Context.Provider 將值傳遞給子組件,而是可以直接從 useFormContext 中獲取,簡化嵌套表單的代碼邏輯。
條件判斷
import React from "react";
import { useForm } from "react-hook-form";
function ExampleForm() {
const { register, handleSubmit, watch } = useForm();
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label htmlFor="hasAge">Do you have an age?</label>
<select {...register("hasAge")}>
<option value="yes">Yes</option>
<option value="no">No</option>
</select>
{watch("hasAge") === "yes" && (
<>
<label htmlFor="age">Age</label>
<input {...register("age", { required: true, min: 18 })} />
{watch("age") && <p>You must be at least 18 years old.</p>}
</>
)}
<button type="submit">Submit</button>
</form>
);
}
export default ExampleForm;
我們在 hasAge 輸入框上使用了一個簡單的條件渲染:只有當(dāng)用戶選擇了 "Yes" 時,才會渲染 age 輸入框。然后使用 watch 函數(shù)來監(jiān)聽輸入框的值,并在輸入的值小于 18 時顯示相應(yīng)的錯誤信息。
watch 函數(shù)用來監(jiān)聽指定的輸入并返回它們的值。在渲染輸入值和進(jìn)行條件渲染時經(jīng)常用到。
表單列表
import React from "react";
import { useForm, useFieldArray } from "react-hook-form";
function ListForm() {
const { register, control, handleSubmit } = useForm({
defaultValues: {
list: [{ name: "" }, { name: "" }, { name: "" }]
}
});
const { fields, append, remove } = useFieldArray({
control,
name: "list"
});
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
{fields.map((field, index) => (
<div key={field.id}>
<input
{...register(`list.${index}.name`, {
required: "This field is required"
})}
defaultValue={field.name}
/>
<button type="button" onClick={() => remove(index)}>
Remove
</button>
</div>
))}
<button type="button" onClick={() => append({ name: "" })}>
Add Item
</button>
<button type="submit">Submit</button>
</form>
);
}
export default ListForm;
分析一下上邊這段代碼:
- 在這個示例中,我們使用了
useForm和useFieldArrayhook 來處理一個表單列表。其中list屬性是一個包含 3 個空對象的數(shù)組。 - 使用
fields.map方法遍歷fields數(shù)組,渲染出每一個列表項(xiàng)。 - 使用
remove方法為每個列表項(xiàng)添加了一個 "Remove" 按鈕,使得用戶可以刪除不需要的列表項(xiàng)。我們還使用append方法添加了一個 "Add Item" 按鈕,可以添加新的列表項(xiàng)。
這段代碼的核心就是 useFieldArray,它專門用于處理表單列表的場景,使用時我們將 useForm 返回的 control 傳入 useFieldArray hook 中,并為這個列表定義一個名字,hook 會為我們返回一些操作列表的方法,在遍歷渲染列表時,我們將每一個子項(xiàng)單獨(dú)進(jìn)行注冊就可以實(shí)現(xiàn)表單列表的動態(tài)數(shù)據(jù)更改了。
需要注意的是,當(dāng)使用 useFieldArray 處理表單中的數(shù)組字段時,每個字段都必須有一個 唯一的 key 值,這樣才能正確地進(jìn)行數(shù)組的添加、刪除、更新等操作。如果數(shù)組中的字段沒有 key 值,useFieldArray 會自動為每個字段生成一個隨機(jī)的 key 值。
在內(nèi)部實(shí)現(xiàn)上,useFieldArray 使用了 useFormContext 將 FormProvider 提供的 register、unregister 和 setValue 函數(shù)傳遞給了 useFieldArray,然后在 useFieldArray 內(nèi)部維護(hù)一個數(shù)組 state,保存當(dāng)前的數(shù)組值和對數(shù)組的操作。
第三方組件
當(dāng)需要與第三方UI組件(如<DatePicker />、<Select />、<Slider />等)集成時,如果使用register 注冊這些第三方UI組件,可能會遇到如無法正確更新表單數(shù)據(jù)、錯誤處理、性能差等問題。
因此,使用Controller 是一種更好的解決方案,可以將表單數(shù)據(jù)與 React Hook Form 狀態(tài)管理集成在一起,并使用render 函數(shù)來直接渲染第三方UI組件。下面放個例子:
import React from "react";
import { useForm, Controller } from "react-hook-form";
import { TextField, Button } from "@material-ui/core";
function ControllerForm() {
const { control, handleSubmit } = useForm();
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="firstName"
control={control}
defaultValue=""
rules={{ required: true }}
render={({ field }) => (
<TextField label="First Name" {...field} />
)}
/>
<Controller
name="lastName"
control={control}
defaultValue=""
rules={{ required: true }}
render={({ field }) => (
<TextField label="Last Name" {...field} />
)}
/>
<Button type="submit" variant="contained" color="primary">
Submit
</Button>
</form>
);
}
export default ControllerForm;
control 是一個對象,它提供了一些方法和屬性,通過使用 control,我們可以將 React Hook Form 中的數(shù)據(jù)與實(shí)際渲染的表單組件進(jìn)行綁定,從而讓 React Hook Form 管理表單中所有的輸入和校驗(yàn)邏輯。
field 是 <Controller> 組件通過 render 回調(diào)函數(shù)傳遞給其子組件的一個對象,field 對象中包含了一些屬性,如 value、onChange、onBlur 等,這些屬性傳遞給子組件,用于設(shè)置和更新表單控件的值,以及處理表單控件的事件,如用戶輸入、聚焦、失焦等。
Controller的好處是可以將表單數(shù)據(jù)和表單狀態(tài)統(tǒng)一管理,同時避免了對表單數(shù)據(jù)的手動處理。此外,它還可以優(yōu)化表單的渲染性能,并提供更好的錯誤處理機(jī)制,因?yàn)樗梢宰詣犹幚礤e誤消息和驗(yàn)證規(guī)則。
Typescript 支持
React Hook Form 提供了完整的 TypeScript 支持:
import React from "react";
import { useForm, SubmitHandler } from "react-hook-form";
type FormValues = {
firstName: string;
lastName: string;
age: number;
};
function MyForm() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormValues>();
const onSubmit: SubmitHandler<FormValues> = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("firstName", { required: true })} />
{errors.firstName && <span>This field is required</span>}
<input {...register("lastName", { required: true })} />
{errors.lastName && <span>This field is required</span>}
<input {...register("age", { required: true, min: 18 })} />
{errors.age && (
<span>
{errors.age.type === "required"
? "This field is required"
: "You must be at least 18 years old"}
</span>
)}
<button type="submit">Submit</button>
</form>
);
}
我們使用 FormValues 類型定義表單數(shù)據(jù)類型,并在 useForm 鉤子中使用 FormValues 泛型接口。這使得我們可以在注冊表單控件時提供正確的類型定義,并在 handleSubmit 函數(shù)中提供正確的表單數(shù)據(jù)類型。還可以使用泛型來定義錯誤消息的類型,可以用于準(zhǔn)確地描述表單控件的錯誤狀態(tài),并提供適當(dāng)?shù)腻e誤消息。提高代碼的可讀性和可維護(hù)性。
總結(jié)
除了上述的一些表單使用姿勢,在官方的 react-hook-form.com/advanced-us… 頁面還可以看到一些高級特性的示例,例如 Schema 解析為表單,實(shí)現(xiàn)表單組件測試,表單與 API 間的數(shù)據(jù)轉(zhuǎn)換 等等。
總的來說,React Hook Form 提供了一種簡單、靈活的方式來管理表單狀態(tài),支持非受控組件方式以及各種復(fù)雜的表單場景,同時也提供了 Typescript 支持,是一個值得嘗試的表單管理庫。
以上就是React Hook Form 優(yōu)雅處理表單使用指南的詳細(xì)內(nèi)容,更多關(guān)于React Hook Form處理表單的資料請關(guān)注腳本之家其它相關(guān)文章!
- React如何利用Antd的Form組件實(shí)現(xiàn)表單功能詳解
- react使用antd的上傳組件實(shí)現(xiàn)文件表單一起提交功能(完整代碼)
- React表單容器的通用解決方案
- react?表單數(shù)據(jù)形式配置化設(shè)計(jì)
- react實(shí)現(xiàn)動態(tài)表單
- React事件處理和表單的綁定詳解
- react表單受控的實(shí)現(xiàn)方案
- React實(shí)現(xiàn)表單提交防抖功能的示例代碼
- React中重新實(shí)現(xiàn)強(qiáng)制實(shí)施表單的流程步驟
- react實(shí)現(xiàn)動態(tài)增減表單項(xiàng)的示例代碼
- React 實(shí)現(xiàn)表單組件的示例代碼
相關(guān)文章
React實(shí)現(xiàn)組件間通信的幾種方式小結(jié)
在React應(yīng)用中,組件間的通信是一個基礎(chǔ)而關(guān)鍵的概念,理解和掌握不同組件之間的通信方式,可以幫助我們構(gòu)建出更加模塊化、可維護(hù)和可擴(kuò)展的應(yīng)用程序,React提供了多種組件通信的方法,本文給大家詳細(xì)的介紹了這些方法,需要的朋友可以參考下2024-07-07
使用react-color實(shí)現(xiàn)前端取色器的方法
本文通過代碼給大家介紹了使用react-color實(shí)現(xiàn)前端取色器的方法,代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2021-11-11
React 并發(fā)功能體驗(yàn)(前端的并發(fā)模式)
React 是由 Facebook 軟件工程師 Jordan Walke 創(chuàng)建,React 的第一個版本在七年前問世,現(xiàn)在,F(xiàn)acebook 負(fù)責(zé)維護(hù),本文給大家介紹React 并發(fā)功能體驗(yàn)前端并發(fā)模式的問題,感興趣的朋友跟隨小編一起看看吧2021-07-07
antd-react使用Select組件defaultValue踩的坑及解決
這篇文章主要介紹了antd-react使用Select組件defaultValue踩的坑及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05
react項(xiàng)目中express動態(tài)路由未能匹配造成的404問題解決
本文主要介紹了react項(xiàng)目中express動態(tài)路由未能匹配造成的404問題解決,解決了白屏的問題,具有一定的參考價值,感興趣的可以了解一下2023-09-09

