
그동안 나는 내가 react-hook-form을 사용할 줄 안다고 착각하고 있었다. 얼추 메서드 사용은 할 줄 아는데 제대로 쓸 줄은 몰랐던 것이었다. 공식문서를 다시 한 번 제대로 읽어보니 react-hook-form은 비제어 컴포넌트 기반이기 때문에 성능 측면에서 장점이 있다는 점을 알게되었다. 여태 그것도 모르고 쓰고 있었다!!
그리고 react-hook-form은 폼 상태를 관리하는 라이브러리인데 상위에 Context provider를 두고 거기에서 상태를 이중으로 관리하고 있었다. 그럴거면 react-hook-form 왜 쓰냐고~~
이렇게 흑역사 코드를 만든 계기로 제어 컴포넌트와 비제어 컴포넌트의 차이에 대해서 공부하는 시간을 가져보았다. 그리고 이번 시간을 계기로 react-hook-form의 장점을 최대한으로 누리는 코드로 고쳐보려고 한다.
react에서 form을 관리할 때 useState으로 input의 상태를 관리하는 코드를 써본 경험은 프론트엔드 개발자라면 다들 있을 것이다.
const [inputState, setInputState] = useState('');
return (
<form>
<input
value={inputState}
onChange={(e) => setInputState(e.target.value)
/>
//...
사용자가 input 값을 한 글자 한 글자 입력할 때마다 inputState이 갱신된다. 그리고 state의 변경이 발생할 때마다 컴포넌트가 리렌더링된다. 글자를 입력할 때마다 자식 컴포넌트들까지 리렌더링될 것이라고 생각하면.. 성능 측면에서 제어 컴포넌트의 단점이 될 것이다.
물론 실시간으로 상태를 추적해야할 경우에는 유리하다.
예를 들어 현재 사용자가 입력한 비밀번호의 유효성 검사를 한다고 생각해보면, (비제어 컴포넌트와 달리) 굳이 버튼을 클릭하지 않아도 실시간으로 입력한 비밀번호의 상태를 추적하여 에러 메시지를 내뱉어주는게 UX적으로 긍정적이다.
반면, state이 아니라 ref를 이용하여 DOM에 직접 데이터를 꽂아넣는 방식으로 입력값을 관리한다면, 해당 컴포넌트는 비제어 컴포넌트가 된다.
const inputRef = useRef(null);
return (
<form
<input
ref={inputRef}
/>
<button
onClick={
console.log(inputRef?.current)
//...
제어 컴포넌트와의 가장 큰 차이는 사용자가 입력한 값의 상태가 실시간으로 상태 관리되지 않고 DOM에 직접 꽂힌다는 점이다. 버튼을 클릭해야만 리렌더링이 되기 때문에, 성능 측면에서는 비교적으로 제어 컴포넌트보다 유리하다고 할 수 있다.
물론 input이 한두개면 제어컴포넌트를 사용하던 비제어 컴포넌트를 사용하던 성능 측면에서는 큰 차이가 없고, 요구사항에 따라 골라서 사용하면 될 것이다. 하지만, 관리해야할 form과 input이 넘쳐난다면 (지금의 나?) 이를 효율적으로 관리하면서 성능 측면도 고려할 수 있는 방법이 필요하다. 바로 내부적으로는 비제어 컴포넌트 기반으로 form을 관리하되, 제어 컴포넌트를 사용하는 것마냥 편리한 메서드를 제공해주는 react-hook-form이다. (이걸 지금 알게 된 나 ㄱ-;;)
지난 번 포스팅에서 react-hook-form을 사용할 때 form 내부의 input을 별도의 컴포넌트로 관리하려면 forwardRef를 사용해서 자식 컴포넌트를 묶어줘야 한다고 설명했었다. 이처럼 register 함수의 반환값에는 ref가 있고, ref를 이용하여 요소를 등록한다는 점에서도 비제어 컴포넌트 기반의 라이브러리라는 사실을 설명할 수 있다.
이제 provider에 대한 의존도를 점차 줄여나가고 react-hook-form의 장점을 최대로 누릴 수 있는 코드로 바꿔보자. context provider 내부의 state 값을 갱신하고 있는 로직을 점진적으로 갖다 버리도록 하겠다. 짜이찌앤!!
참고 자료:
공식 문서
https://react-hook-form.com/get-started