React?Hook?Form?優(yōu)雅處理表單使用指南
受控組件與非受控組件
受控組件
先說說受控組件,以 input 為例:
const [value,setValue] = useState('') <input value={value} onChange={(e)=> setValue(e.target.value)} />
在上面的代碼中,我們通過通過自己維護(hù)一個(gè) 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> ); }
在這個(gè)例子中,我們使用 useRef
Hook 創(chuàng)建了一個(gè) ref 對(duì)象,并將其賦值給每個(gè) input 元素的 ref
屬性。在 handleSubmit
函數(shù)中,我們使用 ref.current.value
來獲取每個(gè) input 元素的值。這里的每個(gè)input 元素都是非受控組件,因?yàn)樗鼈兊闹涤?DOM 節(jié)點(diǎn)直接處理,而不是由 React 組件來管理。
當(dāng)然,這意味著當(dāng)用戶輸入數(shù)據(jù)時(shí),React 無法追蹤表單元素的值。因此,當(dāng)您需要訪問表單元素的值時(shí),您需要使用DOM API來獲取它們。
為什么需要非受控組件
在 React 中,通常使用受控組件來處理表單。受控組件表單元素的值由 React 組件來管理,當(dāng)表單數(shù)據(jù)發(fā)生變化時(shí),React 會(huì)自動(dòng)更新組件狀態(tài),并重新渲染組件。這種方式可以使得表單處理更加可靠和方便,也可以使得表單數(shù)據(jù)和應(yīng)用狀態(tài)之間保持一致。
但在實(shí)際的開發(fā)中,表單往往是最復(fù)雜的場(chǎng)景,有的表單有數(shù)十個(gè)字段,如果使用受控組件去構(gòu)建表單,那么我們就需要維護(hù)大量 state,且 React 又不像 Vue 可以通過雙向綁定直接修改 state 的值,每一個(gè)表單字段還需要定義一下 onChange
方法。因此在維護(hù)復(fù)雜表單時(shí),使用受控組件會(huì)有很大的額外代碼量。
為了解決受控組件帶來的問題,我們可以使用非受控組件來構(gòu)建表單。受控組件主要有以下三個(gè)優(yōu)點(diǎn)
- 可以減少組件的 代碼量和復(fù)雜度,因?yàn)榉鞘芸亟M件不需要在組件狀態(tài)中保存表單數(shù)據(jù)。
- 可以更好地 處理大量表單數(shù)據(jù),因?yàn)榉鞘芸亟M件可以讓您直接操作DOM元素,而不需要將所有表單數(shù)據(jù)存儲(chǔ)在組件狀態(tài)中。
- 可以更容易地與第三方 JavaScript 庫(kù)和表單處理代碼集成,因?yàn)榉鞘芸亟M件使您能夠使用 DOM API 或 ref 直接訪問表單元素,而不是在 React 中重新實(shí)現(xiàn)所有的表單處理邏輯。
React Hook Form 是什么?
React Hook Form 是一個(gè)基于 React 的 輕量級(jí)表單驗(yàn)證庫(kù)。它使用了 React Hook API,讓表單驗(yàn)證變得簡(jiǎn)單、易用、高效。React Hook Form 的主要目標(biāo)是提供一個(gè)簡(jiǎn)單易用的表單驗(yàn)證解決方案,同時(shí)還能保持高性能和低開銷。
React Hook Form 的特點(diǎn)都在官網(wǎng)首頁(yè) react-hook-form.com 中以可交互的形式展示,包括了以下幾點(diǎn):
- 通過 React Hook 的方式減少使用時(shí)的代碼量,簡(jiǎn)單易上手,并且移除了不必要的重復(fù)渲染:
- 隔離重復(fù)渲染,自組件重新渲染時(shí)不會(huì)觸發(fā)父組件或兄弟組件的重新渲染:
- 訂閱機(jī)制,與上一點(diǎn)相似,能夠訂閱單個(gè)輸入和表單狀態(tài)更新,而無需重新呈現(xiàn)整個(gè)表單:
- 組件渲染速度足夠快,而且代碼庫(kù)非常小,壓縮后只有幾 KB 大小,不會(huì)對(duì)頁(yè)面性能造成任何影響:
React Hook Form 的使用姿勢(shì)
數(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)建一個(gè)表單對(duì)象,該函數(shù)返回一個(gè)包含register
和handleSubmit
等方法的對(duì)象。 - 在表單中定義兩個(gè)輸入框,使用
register
函數(shù)注冊(cè)表單輸入組件,并指定組件的名稱為firstName
和lastName
。 - 使用 React Hook Form 提供的
handleSubmit
函數(shù)來管理表單的提交和數(shù)據(jù)驗(yàn)證。
這里我們不需要定義任何 state 即可在 submit 時(shí)獲取到表單中的數(shù)據(jù),接下來我們補(bǔ)充一下基本的表單驗(yàn)證和錯(cuò)誤提示:
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> ); }
咱們?cè)俜治鲆幌逻@段代碼:
register
函數(shù)中可以指定表單輸入組件的 驗(yàn)證規(guī)則 ,例如使用required
規(guī)則來驗(yàn)證輸入框的必填項(xiàng)。- 在表單提交處理函數(shù)中,可以調(diào)用 React Hook Form 提供的
handleSubmit
函數(shù)來自動(dòng)執(zhí)行表單驗(yàn)證,并返回驗(yàn)證結(jié)果。只有表單驗(yàn)證通過才會(huì)執(zhí)行onSubmit
方法。 - 如果表單驗(yàn)證失敗,可以使用
errors
對(duì)象來獲取每個(gè)表單輸入組件的驗(yàn)證錯(cuò)誤信息,并在 UI 上顯示錯(cuò)誤提示。
register
函數(shù)是用來注冊(cè)表單輸入組件的,當(dāng)組件注冊(cè)之后,React Hook Form 會(huì)自動(dòng)收集該組件的值,并根據(jù)驗(yàn)證規(guī)則進(jìn)行驗(yàn)證。 register
函數(shù)會(huì)返回一個(gè)對(duì)象,其中包含了一些屬性和方法,例如:
const { ref, onChange, onBlur, name } = register("firstName");
ref
屬性是一個(gè)引用,指向該輸入組件的 DOM 元素,onChange
和 onBlur
是回調(diào)函數(shù),用于處理該組件的值變化和失去焦點(diǎn)事件,name
是該組件的名稱。
register
函數(shù)內(nèi)部會(huì)創(chuàng)建一個(gè)管理表單輸入組件的對(duì)象,包含了該組件的名稱、引用、驗(yàn)證規(guī)則等信息。同時(shí),還會(huì)將該對(duì)象保存在 React Hook Form 內(nèi)部的一個(gè)數(shù)據(jù)結(jié)構(gòu)中。
在表單提交時(shí),React Hook Form 會(huì)遍歷管理的所有表單輸入組件,并收集它們的值,并根據(jù)其注冊(cè)時(shí)定義的驗(yàn)證規(guī)則進(jìn)行驗(yàn)證。
到這里,一個(gè)最基本的表單驗(yàn)證及提交就已經(jīng)實(shí)現(xiàn)了。當(dāng)然,在實(shí)際開發(fā)中,表單之所以復(fù)雜是由于各種條件渲染及表單嵌套引起的,那么我們接下來再看看使用 React Hook Form 如何處理這些場(chǎng)景。
表單嵌套
還是一樣,先看看示例:
父級(jí)表單:
// 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;
子級(jí)表單:
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;
分析一下這兩個(gè)組件的代碼:
在 ParentForm
組件中,我們使用 useForm
hook 來獲取表單的注冊(cè)函數(shù)、表單狀態(tài)等信息,并使用 FormProvider
組件將其傳遞給所有的子組件。
在 ChildForm
組件中,我們使用了 useFormContext
hook 來獲取父表單的注冊(cè)函數(shù)和表單狀態(tài)。
這里的兩個(gè)表單組件間并不需要咱們?nèi)为?dú)定義 props ,只需要將 useFormContext
與 FormProvider
搭配使用,就可以將一個(gè)嵌套表單的邏輯分離成多個(gè)組件進(jìn)行處理,且可以在父級(jí)組件提交時(shí)統(tǒng)一獲取并處理數(shù)據(jù)。
FormProvider
是 React Hook Form 提供的一個(gè)組件,用于在 React 組件樹中向下傳遞 useForm
hook 的實(shí)例。它創(chuàng)建了一個(gè) React Context,并將 useForm
hook 的實(shí)例作為 Context 的值,然后通過 Context.Provider
組件將這個(gè)值傳遞給所有子組件.
而 useFormContext
則可以在子組件中獲取到 FormProvider
提供的 useForm
hook 的返回值。在使用 useFormContext
時(shí),不需要手動(dòng)使用 Context.Provider
將值傳遞給子組件,而是可以直接從 useFormContext
中獲取,簡(jiǎn)化嵌套表單的代碼邏輯。
條件判斷
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;
我們?cè)?hasAge
輸入框上使用了一個(gè)簡(jiǎn)單的條件渲染:只有當(dāng)用戶選擇了 "Yes" 時(shí),才會(huì)渲染 age
輸入框。然后使用 watch
函數(shù)來監(jiān)聽輸入框的值,并在輸入的值小于 18 時(shí)顯示相應(yīng)的錯(cuò)誤信息。
watch
函數(shù)用來監(jiān)聽指定的輸入并返回它們的值。在渲染輸入值和進(jìn)行條件渲染時(shí)經(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;
分析一下上邊這段代碼:
- 在這個(gè)示例中,我們使用了
useForm
和useFieldArray
hook 來處理一個(gè)表單列表。其中list
屬性是一個(gè)包含 3 個(gè)空對(duì)象的數(shù)組。 - 使用
fields.map
方法遍歷fields
數(shù)組,渲染出每一個(gè)列表項(xiàng)。 - 使用
remove
方法為每個(gè)列表項(xiàng)添加了一個(gè) "Remove" 按鈕,使得用戶可以刪除不需要的列表項(xiàng)。我們還使用append
方法添加了一個(gè) "Add Item" 按鈕,可以添加新的列表項(xiàng)。
這段代碼的核心就是 useFieldArray
,它專門用于處理表單列表的場(chǎng)景,使用時(shí)我們將 useForm 返回的 control 傳入 useFieldArray hook 中,并為這個(gè)列表定義一個(gè)名字,hook 會(huì)為我們返回一些操作列表的方法,在遍歷渲染列表時(shí),我們將每一個(gè)子項(xiàng)單獨(dú)進(jìn)行注冊(cè)就可以實(shí)現(xiàn)表單列表的動(dòng)態(tài)數(shù)據(jù)更改了。
需要注意的是,當(dāng)使用 useFieldArray 處理表單中的數(shù)組字段時(shí),每個(gè)字段都必須有一個(gè) 唯一的 key 值,這樣才能正確地進(jìn)行數(shù)組的添加、刪除、更新等操作。如果數(shù)組中的字段沒有 key 值,useFieldArray
會(huì)自動(dòng)為每個(gè)字段生成一個(gè)隨機(jī)的 key 值。
在內(nèi)部實(shí)現(xiàn)上,useFieldArray
使用了 useFormContext 將 FormProvider 提供的 register
、unregister
和 setValue
函數(shù)傳遞給了 useFieldArray
,然后在 useFieldArray 內(nèi)部維護(hù)一個(gè)數(shù)組 state,保存當(dāng)前的數(shù)組值和對(duì)數(shù)組的操作。
第三方組件
當(dāng)需要與第三方UI組件(如<DatePicker />
、<Select />
、<Slider />
等)集成時(shí),如果使用register
注冊(cè)這些第三方UI組件,可能會(huì)遇到如無法正確更新表單數(shù)據(jù)、錯(cuò)誤處理、性能差等問題。
因此,使用Controller
是一種更好的解決方案,可以將表單數(shù)據(jù)與 React Hook Form
狀態(tài)管理集成在一起,并使用render
函數(shù)來直接渲染第三方UI組件。下面放個(gè)例子:
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
是一個(gè)對(duì)象,它提供了一些方法和屬性,通過使用 control
,我們可以將 React Hook Form 中的數(shù)據(jù)與實(shí)際渲染的表單組件進(jìn)行綁定,從而讓 React Hook Form 管理表單中所有的輸入和校驗(yàn)邏輯。
field
是 <Controller>
組件通過 render
回調(diào)函數(shù)傳遞給其子組件的一個(gè)對(duì)象,field
對(duì)象中包含了一些屬性,如 value
、onChange
、onBlur
等,這些屬性傳遞給子組件,用于設(shè)置和更新表單控件的值,以及處理表單控件的事件,如用戶輸入、聚焦、失焦等。
Controller
的好處是可以將表單數(shù)據(jù)和表單狀態(tài)統(tǒng)一管理,同時(shí)避免了對(duì)表單數(shù)據(jù)的手動(dòng)處理。此外,它還可以優(yōu)化表單的渲染性能,并提供更好的錯(cuò)誤處理機(jī)制,因?yàn)樗梢宰詣?dòng)處理錯(cuò)誤消息和驗(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
泛型接口。這使得我們可以在注冊(cè)表單控件時(shí)提供正確的類型定義,并在 handleSubmit
函數(shù)中提供正確的表單數(shù)據(jù)類型。還可以使用泛型來定義錯(cuò)誤消息的類型,可以用于準(zhǔn)確地描述表單控件的錯(cuò)誤狀態(tài),并提供適當(dāng)?shù)腻e(cuò)誤消息。提高代碼的可讀性和可維護(hù)性。
總結(jié)
除了上述的一些表單使用姿勢(shì),在官方的 react-hook-form.com/advanced-us… 頁(yè)面還可以看到一些高級(jí)特性的示例,例如 Schema 解析為表單,實(shí)現(xiàn)表單組件測(cè)試,表單與 API 間的數(shù)據(jù)轉(zhuǎn)換 等等。
總的來說,React Hook Form 提供了一種簡(jiǎn)單、靈活的方式來管理表單狀態(tài),支持非受控組件方式以及各種復(fù)雜的表單場(chǎng)景,同時(shí)也提供了 Typescript 支持,是一個(gè)值得嘗試的表單管理庫(kù)。
以上就是React Hook Form 優(yōu)雅處理表單使用指南的詳細(xì)內(nèi)容,更多關(guān)于React Hook Form處理表單的資料請(qǐng)關(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)動(dòng)態(tài)表單
- React事件處理和表單的綁定詳解
- react表單受控的實(shí)現(xiàn)方案
- React實(shí)現(xiàn)表單提交防抖功能的示例代碼
- React中重新實(shí)現(xiàn)強(qiáng)制實(shí)施表單的流程步驟
- react實(shí)現(xiàn)動(dòng)態(tài)增減表單項(xiàng)的示例代碼
- React 實(shí)現(xiàn)表單組件的示例代碼
相關(guān)文章
React+Router多級(jí)導(dǎo)航切換路由方式
這篇文章主要介紹了React+Router多級(jí)導(dǎo)航切換路由方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03React實(shí)現(xiàn)組件間通信的幾種方式小結(jié)
在React應(yīng)用中,組件間的通信是一個(gè)基礎(chǔ)而關(guān)鍵的概念,理解和掌握不同組件之間的通信方式,可以幫助我們構(gòu)建出更加模塊化、可維護(hù)和可擴(kuò)展的應(yīng)用程序,React提供了多種組件通信的方法,本文給大家詳細(xì)的介紹了這些方法,需要的朋友可以參考下2024-07-07使用react-color實(shí)現(xiàn)前端取色器的方法
本文通過代碼給大家介紹了使用react-color實(shí)現(xiàn)前端取色器的方法,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-11-11React 并發(fā)功能體驗(yàn)(前端的并發(fā)模式)
React 是由 Facebook 軟件工程師 Jordan Walke 創(chuàng)建,React 的第一個(gè)版本在七年前問世,現(xiàn)在,F(xiàn)acebook 負(fù)責(zé)維護(hù),本文給大家介紹React 并發(fā)功能體驗(yàn)前端并發(fā)模式的問題,感興趣的朋友跟隨小編一起看看吧2021-07-07antd-react使用Select組件defaultValue踩的坑及解決
這篇文章主要介紹了antd-react使用Select組件defaultValue踩的坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05react項(xiàng)目中express動(dòng)態(tài)路由未能匹配造成的404問題解決
本文主要介紹了react項(xiàng)目中express動(dòng)態(tài)路由未能匹配造成的404問題解決,解決了白屏的問題,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09Rect Intersection判斷兩個(gè)矩形是否相交
這篇文章主要為大家介紹了Rect Intersection判斷兩個(gè)矩形是否相交的算法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06React移動(dòng)端項(xiàng)目之pdf預(yù)覽問題
這篇文章主要介紹了React移動(dòng)端項(xiàng)目之pdf預(yù)覽問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02