React Hook Form 의 퍼포먼스
이 커스텀 훅을 만들 때 가장 먼저 고려된 목표 중 하나가 퍼포먼스입니다. React Hook Form 은 비제어 컴포넌트를 활용하고 있기 때문에 ref
에서 register
함수가 실행됩니다. 이러한 접근 방식은 사용자가 타이핑하거나 값을 변경할 때 리랜더링이 일어나는 양을 줄여줄 것입니다. 제어 컴포넌트가 아니기 때문에 페이지에 컴포넌트가 마운트되는 속도도 훨씬 더 빠릅니다. 마운트되는 속도에 대해 여러분이 참고하실 수 있도록 간단한 속도 비교 테스트를 이 저장소에 올려 두었습니다.
접근성 있는 입력 에러와 메세지를 어떻게 만드나요?
React Hook Form 은 비제어 컴포넌트를 기반으로 하고 있으므로, 접근성이 높은 커스텀 폼을 쉽게 만들 수 있습니다.
import React from "react"; 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> <input id="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> ); }
클래스 컴포넌트와 사용할 수 있나요?
아니오. 사용할 수 없습니다만, 클래스 컴포넌트를 감싸는 래퍼를 만들 수는 있습니다.
여러분은 클래스 컴포넌트 안에서 훅을 사용할 수 없습니다. 하지만 확실히 클래스 컴포넌트와 훅을 사용하는 함수 컴포넌트를 같은 트리 안에서 섞어 사용할 수 있습니다. 컴포넌트가 클래스인지, 훅을 사용하는 함수 컴포넌트인지는 개별 컴포넌트의 세부 구현에 불과합니다. 긴 안목으로 보아 우리는 사람들이 훅을 우선적으로 고려하여 리액트 컴포넌트를 작성하길 기대합니다.
폼을 어떻게 리셋하나요?
폼을 초기화하는데 두 가지 방법이 있습니다.
- HTMLFormElement.reset()
이 메서드는 폼의 리셋 버튼을 누르는 것과 똑같이 동작하지만, 오로지
input/select/checkbox
값들만 초기화합니다. - React Hook Form API:
reset()
React Hook Form 의
reset
메서드는 모든 필드 값을 리셋하며, 또한 폼 안의 모든errors
를 초기화합니다.
어떻게 폼의 기본값을 설정하나요?
React Hook Form 은 비제어 컴포넌트를 활용합니다. 비제어 컴포넌트를 사용하면 defaultValue
나 defaultChecked
값을 개별 필드에 넣어 기본값을 설정할 수 있습니다. 하지만 훅에서 기본적으로 손쉽게 모든 인풋의 기본값을 설정할 수 있는 방법을 제공합니다. 아래의 예제를 보세요.
import React from "react"; import { useForm } from "react-hook-form"; export default function App() { const { register, handleSubmit } = useForm({ defaultValues: { firstName: "bill", lastName: "luo", email: "bluebill1049@hotmail.com" } }); const onSubmit = data => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register("firstName")} /> <input {...register("lastName")} /> <input {...register("email")} /> <button type="submit">Submit</button> </form> ); }
ref 를 공유할 수 있나요?
React Hook Form 은 입력 값을 모으기 위해 ref
를 필요로 합니다. 하지만 ref
를 다른 목적으로 (예: 해당 뷰로 스크롤하기) 활용하고 싶을 수도 있습니다. 아래의 예제로 그 방법을 확인해보세요.
import React, { useRef } from "react"; import { useForm } from "react-hook-form"; export default function App() { const { register, handleSubmit } = useForm(); const firstNameRef = useRef(null); const onSubmit = data => console.log(data); const { ref, ...rest } = register('firstName'); return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...rest} name="firstName" ref={(e) => { ref(e) firstNameRef.current = e // you can still assign to ref }} /> <button>Submit</button> </form> ); }
import React, { useRef } from "react"; import { useForm } from "react-hook-form"; type Inputs = { firstName: string, lastName: string, }; export default function App() { const { register, handleSubmit } = useForm<Inputs>(); const firstNameRef = useRef<HTMLInputElement | null>(null); const onSubmit = data => console.log(data); const { ref, ...rest } = register('firstName'); return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...rest} name="firstName" ref={(e) => { ref(e) firstNameRef.current = e // you can still assign to ref }} /> <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"); }, []); return ( <form onSubmit={handleSubmit(onSubmit)}> <input name="firstName" onChange={e => setValue("firstName", e.target.value)} /> <input name="lastName" onChange={e => { const value = e.target.value; if (value === "test") { setError("lastName", "notMatch") } else { setValue("lastName", e.target.value) } }} /> <button>Submit</button> </form> ); }
브라우저 호환성은 어떤가요?
React Hook Form 은 모든 메이저 브라우저를 지원합니다.
오래된 IE11 를 지원하려면, react-hook-form IE11 버전을 불러와서 사용할 수 있습니다.
import { useForm } from 'react-hook-form/dist/index.ie11'; // V6 import { useForm } from 'react-hook-form/dist/react-hook-form.ie11'; // V5' // Resolvers import { yupResolver } from '@hookform/resolvers/dist/ie11/yup';
왜 첫 번째 키 입력이 동작하지 않을까요?
value
대신에 defaultValue
를 사용하고 있는지 다시 확인해주세요.
React Hook Form 은 비제어 컴포넌트를 활용하기 때문에 onChange
를 사용하여 state
를 바꾸고, 그 값을 인풋의 value
에 반영해줄 필요가 없습니다. 따라서 value
자체가 필요 없습니다. 사실 초기 값을 지정하고자 할 때 defaultValue
만 넣어주면 됩니다.
MutationObserver 때문에 테스트가 실패하는데요?
만약에 테스트하는데 어려움을 겪고 계시다면 MutationObserver
때문입니다. mutationobserver-shim
패키지를 설치하고 setup.js 파일에서 불러오세요.
React Hook Form 을 Formik, Redux Form 과 비교한다면?
먼저, 모든 라이브러리들은 폼을 만드는 과정을 쉽고 좋게 만들겠다는 공통의 문제를 해결하려고 합니다. 하지만 세 라이브러리는 약간 근본적인 차이를 가지고 있고, react-hook-form 는 기본적으로 비제어 컴포넌트를 활용하고 있습니다. 그리하여 여러분의 폼이 최대한의 퍼포먼스를 내고 최소한의 리랜더링만 발생하도록 합니다. 그 위에 react-hook-form 은 리액트 훅을 기반으로 만들어졌으며 훅으로 사용됩니다. 따라서 별도의 컴포넌트를 불러올 필요가 없습니다. 아래에 더 자세한 차이점을 표기했습니다.
React Hook Form | Formik | Redux Form | |
---|---|---|---|
컴포넌트 | 비제어 & 제어 | 제어 | 제어 |
랜더링 | 최소한의 리랜더링 | 인풋에 타이핑하면서 지역 상태가 변할떄마다 리랜더링 | 인풋에 타이핑하면서 상태 관리 라이브러리(Redux)의 상태가 바뀔 때마다 리랜더링 |
API | 훅 | 컴포넌트 (RenderProps, Form, Field) + 훅 | 컴포넌트 (RenderProps, Form, Field) |
패키지 크기 | 작음react-hook-form@6.0.0 | 중간formik@2.1.4 | 큼redux-form@8.3.6 |
유효성 검사 | 내장됨, Yup, Joi, Superstruct 혹은 직접 제작 가능 | 직접 만들어야 하거나 Yup | 직접 만들어야 하거나 플러그인 |
러닝 커브 | 낮음 | 중간 | 중간 |
현황 | 중간 수준의 커뮤니티이며 성장 중 | 큰 커뮤니티: 커뮤니티 안에서 잘 정립된 폼 라이브러리 | 큰 커뮤니티: 커뮤니티 안에서 잘 정립된 폼 라이브러리 |
제어 컴포넌트와 조합할 수 있나요?
짧게 답변드리면, 할 수 있습니다.
React-hook-form 은 사용자가 제어되는 폼을 만들도록 권장하진 않지만 제어 컴포넌트와 쉽게 조합할 수 있습니다.
그 방법은 watch
API 를 사용하여 각 인풋 값의 변화를 관찰하고 value prop 에 할당하는 것입니다.
또는 감싸는 컴포넌트인 Controller 을 사용하여 커스텀 레지스터를 관리 할 수 있습니다.
import React from "react"; import { useForm, Controller } from "react-hook-form"; function App() { const { control } = useForm(); return ( <Controller render={({ field }) => <input {...field} />} name="firstName" control={control} defaultValue="" /> ); }
React Hook Form 테스트하기
왜 리액트 네이티브 환경(
react-native-testing-library
)에서 테스트가 되지 않나요?React Hook Form 은 서버 사이드 랜더링 중에는 인풋을 등록할 수 없습니다. 따라서 리액트 네이티브 환경에서 테스트 할 때
window
객체가undefined
가 됩니다. 간단히 고치는 방법으로window
객체를 스텁(stub)으로 만들면 등록이 가능합니다.왜
act
경고가 뜨나요?React Hook Form 의 모든 유효성 검사 메서드는 async 함수로 처리됩니다. 따라서 act 함수 실행 시
async
를 감싸주는 것이 중요합니다.왜 입력값을 바꿀 때 이벤트가 발생하지 않나요?
React Hook Form 은 입력값이 바뀔 때
input
이벤트를 사용합니다. 만약 react-testing-library 를 사용하고 계시다면, 손쉽게fireEvent.input
로 바꾸면 됩니다. 여기 예제 링크가 있습니다.만약 enzyme 를 사용하고 계시다면, 인풋의 DOM 노드에 수동으로
value
를 설정해주시고, input 이벤트를 실행하셔야 합니다.const element = wrapper.find("select[data-testid='a']"); element.getDOMNode().value = "foo"; element.getDOMNode().dispatchEvent(new Event("input"));
여러분의 도움이 필요합니다
React Hook Form 이 유용한 프로젝트라고 생각하신다면, Star 를 눌러서 저장소와 기여하는 분들을 응원해주세요 ❤