React Hook Form의 성능
성능은 이 라이브러리가 만들어진 주요 이유 중 하나입니다. React Hook Form은 비제어 컴포넌트 방식을 사용하며, 이것이 register 함수가 ref를 캡처하고 제어 컴포넌트가 Controller 또는 useController로 자체 리렌더링 범위를 갖는 이유입니다. 이 접근 방식은 사용자가 입력 필드에 타이핑하거나 다른 폼 값이 변경될 때 폼이나 애플리케이션의 루트에서 발생하는 리렌더링의 양을 줄입니다. 비제어 컴포넌트는 오버헤드가 적기 때문에 제어 컴포넌트보다 페이지에 더 빠르게 마운트됩니다. 참고로 이 저장소 링크에서 간단한 성능 비교 테스트를 확인할 수 있습니다.
어떻게 접근 가능한 입력 에러 및 메시지를 만드나요?
React Hook Form은 비제어 컴포넌트를 기반으로 하므로, 접근 가능한 커스텀 폼을 쉽게 만들 수 있습니다. (비제어 컴포넌트에 대한 자세한 내용은 컴포넌트 간 State 공유하기를 참고하세요)
import { useForm } from "react-hook-form"export default function App() {const {register,handleSubmit,formState: { errors },} = useForm()const onSubmit = (data) => console.log(data)return (<form onSubmit={handleSubmit(onSubmit)}><label htmlFor="firstName">First name</label><inputid="firstName"aria-invalid={errors.firstName ? "true" : "false"}{...register("firstName", { required: true })}/>{errors.firstName && <span role="alert">This field is required</span>}<input type="submit" /></form>)}
클래스 컴포넌트에서 동작하나요?
아니요, 기본적으로는 작동하지 않습니다. 사용하고 싶다면 wrapper를 만들어서 클래스 컴포넌트에서 사용할 수 있습니다.
클래스 컴포넌트 내부에서는 Hook을 사용할 수 없지만, 하나의 트리 안에서 클래스 컴포넌트와 Hook을 사용하는 함수 컴포넌트를 혼합하여 사용할 수는 있습니다. 컴포넌트가 클래스인지 Hook을 사용하는 함수인지는 단순히 해당 컴포넌트의 구현 세부 사항일 뿐입니다. 장기적으로 우리는 Hook이 React 컴포넌트를 작성하는 주요 방법이 될 것으로 기대합니다.
폼을 어떻게 리셋하나요?
폼을 리셋하는 두 가지 방법이 있습니다:
-
HTMLFormElement.reset()
이 메서드는 폼의 리셋 버튼을 클릭하는 것과 동일한 동작을 수행합니다.
input/select/checkbox값만 초기화합니다. -
React Hook Form API:
reset()React Hook Form의
reset메서드는 모든 필드 값을 리셋하고, 폼 내의 모든errors도 제거합니다.
어떻게 폼 값을 초기화하나요?
React Hook Form은 비제어 컴포넌트 방식을 사용하므로, 개별 필드에 defaultValue 또는 defaultChecked를 지정할 수 있습니다. 하지만 useForm에 defaultValues를 전달하여 폼을 초기화하는 것이 더 일반적이고 권장되는 방법입니다.
function App() {const { register, handleSubmit } = useForm({defaultValues: {firstName: "bill",lastName: "luo",},})return (<form onSubmit={handleSubmit((data) => console.log(data))}><input {...register("firstName")} /><input {...register("lastName")} /><button type="submit">Submit</button></form>)}
비동기 기본값의 경우 다음 방법을 사용할 수 있습니다:
-
비동기
defaultValuesfunction App() {const { register, handleSubmit } = useForm({defaultValues: async () => {const response = await fetch("/api")return await response.json() // return { firstName: '', lastName: '' }},})} -
반응형
valuesfunction App() {const { data } = useQuery() // data는 { firstName: '', lastName: '' }를 반환const { register, handleSubmit } = useForm({values: data,resetOptions: {keepDirtyValues: true, // 변경된 필드는 그대로 유지하되, defaultValues는 업데이트},})}
어떻게 ref의 사용을 공유하나요?
React Hook Form은 입력 값을 수집하기 위해 ref가 필요합니다. 하지만 다른 목적(예: 뷰로 스크롤하거나 포커스)으로 ref를 사용하고 싶을 수도 있습니다.
import { useRef, useImperativeHandle } from "react"import { useForm } from "react-hook-form"type Inputs = {firstName: stringlastName: string}export default function App() {const { register, handleSubmit } = useForm<Inputs>()const firstNameRef = useRef<HTMLInputElement>(null)const onSubmit = (data: Inputs) => console.log(data)const { ref, ...rest } = register("firstName")const onClick = () => {firstNameRef.current.value = ""}useImperativeHandle(ref, () => firstNameRef.current)return (<form onSubmit={handleSubmit(onSubmit)}><input {...rest} ref={firstNameRef} /><button type="button" onClick={onClick}>clear</button><button>Submit</button></form>)}
ref에 접근할 수 없는 경우는 어떻게 해야 하나요?
실제로 ref 없이도 입력을 register할 수 있습니다. 사실, 수동으로 setValue, setError, trigger를 사용할 수 있습니다.
참고: ref가 등록되지 않았기 때문에 React Hook Form은 입력에 이벤트 리스너를 등록할 수 없습니다. 즉, 값과 에러를 수동으로 업데이트해야 합니다.
import React, { useEffect } from "react"import { useForm } from "react-hook-form"export default function App() {const { register, handleSubmit, setValue, setError } = useForm()const onSubmit = (data) => console.log(data)useEffect(() => {register("firstName", { required: true })register("lastName")}, [register])return (<form onSubmit={handleSubmit(onSubmit)}><inputname="firstName"onChange={(e) => setValue("firstName", e.target.value)}/><inputname="lastName"onChange={(e) => {const value = e.target.valueif (value === "test") {setError("lastName", "notMatch")} else {setValue("lastName", e.target.value)}}}/><button>Submit</button></form>)}
첫 번째 키 입력이 동작하지 않는 이유는 무엇인가요?
value를 사용하지 않는지 확인하세요. 올바른 속성은 defaultValue입니다.
React Hook Form은 비제어 입력에 중점을 두고 있으므로, onChange를 통해 state로 입력 value를 변경할 필요가 없습니다. 사실 value는 전혀 필요하지 않습니다. 초기 입력 값을 설정하기 위해 defaultValue만 설정하면 됩니다.
React Hook Form, Formik 또는 Redux Form?
우선, 모든 라이브러리는 동일한 문제를 해결하려고 합니다: 폼 구축 경험을 최대한 쉽게 만드는 것입니다. 하지만 이 세 가지 사이에는 몇 가지 근본적인 차이점이 있습니다. react-hook-form은 비제어 입력을 염두에 두고 만들어졌으며, 가능한 최고의 성능과 최소한의 리렌더링을 제공하려고 합니다. 또한 react-hook-form은 React Hook으로 만들어졌고 훅으로 사용되므로, 임포트할 컴포넌트가 없습니다. 다음은 세부적인 차이점입니다:
| React Hook Form | Formik | Redux Form | |
|---|---|---|---|
| 컴포넌트 | 비제어 & 제어 | 제어 | 제어 |
| 렌더링 | 최소 리렌더링 및 계산 최적화 | 로컬 상태 변경에 따라 리렌더링 (입력할 때마다) | 상태 관리 라이브러리(Redux) 변경에 따라 리렌더링 (입력할 때마다) |
| API | Hooks | Component (RenderProps, Form, Field) + Hooks | Component (RenderProps, Form, Field) |
| 패키지 크기 | 작음 react-hook-form@7.27.0 8.5KB | 보통 formik@2.1.4 15KB | 큼 redux-form@8.3.6 26.4KB |
| 유효성 검사 | 내장, Yup, Zod, Joi, Superstruct 및 직접 구현 가능 | 직접 구현 또는 Yup | 직접 구현 또는 플러그인 |
| 학습 곡선 | 낮음에서 보통 | 보통 | 보통 |
watch vs getValues vs state
- watch: 이벤트 리스너를 통해 모든 입력 또는 특정 입력의 변경 사항을 구독하고, 구독된 필드에 따라 리렌더링합니다. 실제 동작은 이 codesandbox를 확인하세요.
- getValues: 커스텀 훅 내부에 참조로 저장된 값을 가져옵니다. 빠르고 가볍습니다. 이 메서드는 리렌더링을 트리거하지 않습니다.
- 로컬 state: React 로컬 상태는 단순히 입력의 상태뿐만 아니라 무엇을 렌더링할지도 결정합니다. 각 입력의 변경마다 트리거됩니다.
삼항 연산자로 기본값이 올바르게 변경되지 않는 이유는 무엇인가요?
React Hook Form은 전체 폼과 입력을 제어하지 않기 때문에, React는 실제 입력이 교체되거나 바뀌었다는 것을 인식하지 못합니다. 해결 방법으로, 입력에 고유한 key prop을 부여하여 이 문제를 해결할 수 있습니다. key prop에 대한 자세한 내용은 Kent C. Dodds가 작성한 이 글에서도 확인할 수 있습니다.
import { useForm } from "react-hook-form"export default function App() {const { register } = useForm()return (<div>{watchChecked ? (<input {...register("input3")} key="key1" defaultValue="1" />) : (<input {...register("input4")} key="key2" defaultValue="2" />)}</div>)}
모달 또는 탭 폼과 함께 작업할 때는 어떻게 해야 하나요?
React Hook Form이 각 입력 내부에 입력 상태를 저장하여 네이티브 폼 동작을 따른다는 것을 이해하는 것이 중요합니다(useEffect에서 커스텀 register를 사용하는 경우 제외). 흔한 오해는 입력 상태가 마운트되거나 언마운트된 입력에 유지된다고 생각하는 것입니다. 예를 들어 모달이나 탭 폼을 다룰 때 그렇습니다. 올바른 해결책은 각 모달이나 탭 내부에 새로운 폼을 만들고, 제출 데이터를 로컬 또는 전역 상태에 캡처한 다음 결합된 데이터로 작업하는 것입니다.
또는 useForm을 호출할 때 더 이상 사용되지 않는 옵션인 shouldUnregister: false를 사용할 수도 있습니다.
import { useForm, Controller } from "react-hook-form"function App() {const { control } = useForm()return (<Controllerrender={({ field }) => <input {...field} />}name="firstName"control={control}defaultValue=""/>)}
지원해 주셔서 감사합니다
프로젝트에서 React Hook Form이 유용하다고 생각하신다면, 스타를 눌러 지원해 주시길 부탁드립니다.