React Hook Form 도입하기
기존에 react로 form태그를 controlled component 방식으로 구현했을때, 텍스트의 유효성을 검사하고 form에서 일어나는 이벤트를 핸들링하기 위한 로직들이 늘어나며 코드가 너무 복잡해졌다. 이를 해결하고자 react-hook-form을 도입하게 되었다. react-hook-form이 어떻게 위와 같은 문제를 해결해주는지 알아보고, 기본적인 문법과 예시를 알아보겠다.
기존 React만을 사용해 폼을 핸들링했을 경우의 문제점
- form을 다루기 위한 로직을 직접 작성하였다. form을 다루기 위한 로직의 종류들:
- 각각의 input value를 state로 관리하고, 검증하기
- 유효하지 않은 input value에 대한 에러 메시지 관리하기
- 폼 제출 다루기
export default function Form() {
// react state를 이용한 input value 관리
const [passwordInputValue, setPasswordInputValue] = useState < string > "";
const [emailInputValue, setEmailInputValue] = useState < string > "";
const [checkedTerms1, setCheckedTerms1] = useState < boolean > false;
// 유효성 검사 결과도 state로 관리
const [validationMessage, setValidationMessage] = useState < string > "";
const handleSubmit = () => {
const result = {
email: emailInputValue,
password: passwordInputValue,
};
alert("submit completed!" + JSON.stringify(result));
};
const emailValidator = (value: string) => {
const regex = /^[\w\W-\.]+@([\w-]+\.)+[\w-]{2,4}$/;
if (value.length === 0) {
setValidationMessage("이메일은 필수입니다");
} else {
if (regex.test(value)) {
setValidationMessage("");
} else {
setValidationMessage("유효한 이메일 양식이 아닙니다");
}
}
};
const passwordValidator = (value: string) => {
const regex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&_])[\w\W]{8,}$/;
return regex.test(value) || "8자리 이상의 영문, 숫자, 특수문자";
};
const handleEmailInputChange = (e: React.FormEvent<HTMLInputElement>) => {
setEmailInputValue(e.currentTarget.value);
emailValidator(e.currentTarget.value);
};
}
(아주 간단한 email, password, 약관동의 check box를 입력받는 예제에서 필요한 로직이다. 더 복잡한 폼이라면 더 많은 로직이 필요할 것이다.)
- 제어컴포넌트를 이용해 관리해 타이핑이 일어날 때마다(onChange이벤트가 호출될 때마다) 전체 form컴포넌트가 리렌더링된다.
controlled, uncontrolled component에 대한 좀 더 자세한 설명은 여기를 참고
React Hook Form이란?
uncontrolled component를 베이스로 form을 핸들링하는 라이브러리로 최소한의 리렌더링을 통한 높은 성능을 제공한다.
위에서 uncontrolled로 작성했던 코드를 React Hook Form을 사용해 작성해보았다. 우선 email input 한 가지 요소만 react hook form을 도입해본다면 기존 로직을 다음과 같이 변형할 수 있다.
기존 로직)
export default From = () => {
const [emailInputValue, setEmailInputValue] = useState < string > "";
const [validationMessage, setValidationMessage] = useState < string > "";
const emailValidator = (value: string) => {
const regex = /^[\w\W-\.]+@([\w-]+\.)+[\w-]{2,4}$/;
if (value.length === 0) {
setValidationMessage("이메일은 필수입니다");
} else {
if (regex.test(value)) {
setValidationMessage("");
} else {
setValidationMessage("유효한 이메일 양식이 아닙니다");
}
}
};
const handleEmailInputChange = (e: React.FormEvent<HTMLInputElement>) => {
setEmailInputValue(e.currentTarget.value);
emailValidator(e.currentTarget.value);
};
return (
<form onSubmit={handleSubmit} className="register-form">
<div className="form-item">
<label htmlFor="email">Email: </label>
<input
id="email"
type="text"
value={emailInputValue}
onChange={handleEmailInputChange}
autoComplete="off"
pattern="[A-Za-z-\.]+@([a-z-]+\.)+[\w-]{2,4}"
required
/>
<p className="error-message">이메일을 확인해주세요</p>
</div>
</form>
);
};
react-hook-form)
export default Form = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormValues>();
return(
<form onSubmit={handleSubmit(onSubmit)} className="register-form">
<div className="form-item">
<label htmlFor="email">Email: </label>
<input
type="text"
{...register("email", {
required: {
value: true,
message: "이메일은 필수입니다",
},
pattern: {
value: /^[A-Za-z-.]+@([a-z-]+.)+[w-]{2,4}/i,
message: "유효한 이메일 양식이 아닙니다",
},
})}
/>
<p className="error-message">{errors?.email?.message}</p>
</div>
</form>
);
};
useForm
hook으로부터나온 register
, handleSubmit
, formState
등의 메서드를 통해 input 값을 검증하고, 폼 이벤트를 핸들링할 수 있다.
- useForm이 제공하는 기능들
- register: react-hook-form의 가장 주요한 기능이라고 볼 수 있다. input 또는 select 태그 안에서 spread 구문으로 작성되어 기본 HTML input/select 태그를 확장해주는 역할을 한다. 각각의 form 요소와 해당 요소의 검증규칙을 등록할 수 있다.
- watch: 특정한 요소의 input value를 얻을 수 있다. 이 메서드를 통해 특정한 input 값의 상태에 따라 조건부로 렌더링을 시켜주어야할 때 유용하게 사용된다.
- handleSubmit: 파리미터로 두 개의 콜백함수를 받는데, 첫 번째 함수는 폼이 성공적으로 제출되었을 때 실행되는 콜백함수이고, 두 번째 콜백함수는 폼이 검증 규칙을 통과하지 못했을 때 실행되는 콜백함수이다.
개선점
- 코드가 간결해짐 & 코드 길이 축소
기존 로직에서 4개의 state, 5개의 함수가 필요했던 것에 비해 react-hook-form을 도입한 로직에서는 useForm 선언, onSubmit함수 선언만으로 충분했다. (물론 후자의 경우 렌더링 로직이 추가되었다)
코드의 길이도 축소되었다.
- 기존 로직: 약 50줄
- react-hook-form: 약 40줄
이것저것 생략된 코드라 정확한 비교는 아니긴하지만, 확실히 코드량이 줄었다. 물론! 코드량이 줄었다고 해서 무조건 좋아졌다라고 하긴 어렵다.
- 선언적인 코드
내가 생각하는 react-hook-form의 큰 장점이다. react-hook-form이 제공해주는 register
메서드를 통해 input의 값을 검증하는 로직에서 각각의 규칙에 대한 error message를 선언적으로, 그리고 이해하기 쉽게 작성할 수 있게 되었다.
- 성능 개선
react-hook-form 공식문서에서 제공해주는 정보에 따르면 uncontrolled component 방식을 사용하는 라이브러리여서 그런지 성능면에서 뛰어나다고 한다. (다만, 간단한 폼의 경우에는 성능 차이가 크지 않을 것 같다.)
두 가지 버전에 대한 코드는 이 레포지토리에서 확인할 수 있다.
결론
react-hook-form은 uncontrolled 방식으로 form을 정의하여 각각의 input을 isolate하게 렌더링시켜줌으로써 높은 성능을 제공하고, 여러 추상화 메서드들을 이용해 선언적이고 가독성 높은 방식으로 form을 다룰 수 있게 해준다.
form을 핸들링하는 로직을 좀 더 간결하게 작성하고자할 때, 성능면에서 더 나은 방식을 선택하고자할 때 고려해보면 좋은 라이브러리라고 생각한다. (경우에 따라서 controlled component를 사용해야하는 경우 controlled componenet방식으로도 폼을 작성할 수 있는 기능을 제공하니 상황에 따라 유연하게 사용할 수도 있겠다.)