react · high priority
React Forms — Controlled vs Uncontrolled, React Hook Form, Zod
폼은 왜 그렇게 느려지는가 — re-render 비용을 0 으로 만드는 ref 기반 설계와 스키마 기반 검증
학습 개요
선행 학습
- react-hooks-deep
탄생 배경
쉬운 설명
복잡한 개념을 실생활 비유로 설명합니다.
“음식점 주문 — 키오스크 vs 종이 주문서”
Controlled 폼은 손님이 메뉴를 한 번 누를 때마다 점원이 "지금까지 주문하신 게 김치찌개 한 개, 추가로 김치찌개 한 개..." 하고 전체 주문을 매번 재확인하는 키오스크입니다. 빠르게 누르면 점원이 따라오질 못해요. Uncontrolled 폼은 손님이 종이에 쭉 적다가 다 끝났다고 손을 들면 점원이 그때 한 번에 받아 적는 종이 주문서입니다. 점원 입장에서 일이 0 입니다 — 손님이 적는 동안엔. React Hook Form 은 이 종이 주문서 모델 위에서 추가로 "이 줄에 글씨가 잘못됐는지" 만 별도 검사관이 한 줄씩 본다는 식으로 검증을 분리한 것입니다.
핵심 개념
컴포넌트가 input 의 value 를 React state 로 들고 매 변경마다 setState 하면 **controlled**, 그냥 DOM 이 알아서 값을 들고 있고 우리는 ref 로 필요할 때 읽기만 하면 **uncontrolled** 다. 둘 중 어느 쪽이 옳은가가 아니라, **언제 어느 쪽이 적합한가** 가 질문이다.
Controlled vs Uncontrolled
value와onChange가 한 쌍 — React 가 진실의 원천- 키 입력마다 setState → 부모 트리 리렌더
- 실시간 검증·포맷팅·다른 필드와의 동기화에 자연스러움
- 필드 5~10개 짜리 작은 폼, 결제/검색바 등 즉시 반응 UI 에 적합
1const [name, setName] = useState('');2return (3 <input4 value={name}5 onChange={(e) => setName(e.target.value)}6 />7);
defaultValue만 주고 값은 DOM 이 보유- 키 입력에 React 가 관여하지 않음 → 0 리렌더
- 값이 필요할 때만 ref 로 읽음 (보통 submit 시점)
- 큰 폼 전체에서 압도적으로 빠름. RHF 가 채택한 모델
1const ref = useRef<HTMLInputElement>(null);2return (3 <form onSubmit={() => console.log(ref.current?.value)}>4 <input ref={ref} defaultValue="" />5 </form>6);
큰 폼에서 controlled 가 느린 진짜 이유
controlled 모델은 한 글자 입력마다 → setState → 폼 컴포넌트 리렌더 → 모든 자식 필드 리렌더 → 각 필드가 props 비교/스타일 재계산. 30 필드 + 각 필드에 select/checkbox 가 섞이면 한 입력에 수백 개의 가상 DOM 노드가 다시 만들어진다. React Profiler 에서 한 글자에 20-50ms 가 찍히는 게 이 패턴의 전형이다.
| 상황 | 권장 | 이유 |
|---|---|---|
| 필드 5개 이내 + 실시간 동기화 필요 | Controlled | state 동기화 비용 < 코드 복잡도 절감 |
| 필드 20개 이상 결제/온보딩 | Uncontrolled (RHF) | 리렌더 비용이 폭발 → ref 기반이 압도적 |
| 검색바 + 디바운스 + 즉시 결과 | Controlled | 입력 자체가 검색 트리거 |
| 파일 업로드 / Date picker / Rich text | Uncontrolled | 복잡한 native 위젯과 ref 친화 |
| 서버 액션 form (Next 15) | 둘 다 가능 | progressive enhancement 가 우선이면 native form 유지 |
실무 적용
어떤 상황에서 사용하는가
결제 페이지가 입력할 때마다 한 글자씩 늦게 찍힌다는 리포트. 필드는 22개(주소·카드·할인코드·동의 체크 다수). 현재는 모든 필드가 useState 로 controlled.
어떻게 적용하는가
먼저 React Profiler 로 한 글자 입력에 commit 시간이 30-50ms 인 것을 확인. RHF 로 마이그레이션하되 한 번에 다 바꾸지 않고 가장 트래픽 높은 결제 폼부터 점진적으로 — useState 들을 useForm 의 register 로 교체, 기존 onSubmit 에 들어가던 검증 로직을 Zod 스키마로 뽑아 zodResolver 로 연결. 라이브러리 컴포넌트(Date picker 등) 만 Controller 로 감싸 controlled 로 둠. mode 는 onTouched 로 두어 첫 touch 후엔 즉시 피드백, 그 전엔 조용히. Profiler 재측정으로 한 글자 commit 이 1-2ms 까지 떨어지는지 확인.
흔한 실수와 안티패턴
- useState 와 RHF 를 섞어 쓰면 또 그 useState 가 리렌더 원인이 됨 — 완전히 RHF 한 모델로 통일
- Controller 를 모든 필드에 사용 — 그러면 RHF 의 핵심 이득이 사라짐. 정말 필요한 필드만
- `register("a")` 의 `a` 를 동적 키로 만들 때 join 실수 — TypeScript 가 잡아주지만 인덱스 시그니처를 정확히 정의해야
- 서버 검증을 생략하고 클라 Zod 만 신뢰 — DevTools 로 우회 가능, 동일 스키마를 서버에서도 반드시 적용
- onChange 모드를 켜고 비동기 검증(중복 이메일) 을 매 입력마다 호출 — 디바운스 또는 onBlur 로 옮길 것
면접 질문
답변 방향 힌트
Controlled 모델에서 setState → 리렌더 사이클이 어떻게 폼 전체에 퍼지는지, 그리고 그것을 끊는 두 가지 방법.
반드시 언급할 키워드
- Controlled 모델은 onChange 마다 setState → 폼 컴포넌트 리렌더 → 모든 자식 필드 리렌더
- 필드 수가 많아질수록 한 입력의 commit 시간이 선형 증가
- 접근 1: 모델 자체를 uncontrolled(ref) 로 — RHF 가 표준. 리렌더 0
- 접근 2: Controlled 유지하되 필드 단위로 컴포넌트 분리 + memo + 자체 state
- 근본 해결은 1번 — React.memo, useCallback 으로 우회는 한계가 큼
예상 꼬리 질문
- React Hook Form 의 Controller 는 언제 써야 하고, 쓰면 어떤 비용이 생기나요?
- useTransition 으로 input 을 감싸면 큰 폼이 빨라진다는 주장에 대해 어떻게 반박할 건가요?
자기 점검
스크롤 올리지 말고 답해보세요. Controlled 와 Uncontrolled 컴포넌트의 차이를 한 문장으로.
기대 키워드
자주 하는 오해
"controlled 가 항상 옳다" 고 외우는 경우가 많습니다. 작은 폼에서는 맞지만 큰 폼에서는 ref 기반 uncontrolled 가 성능 면에서 압도적이고, 두 모델은 trade-off 가 다릅니다.
React Hook Form 이 폼 전체 리렌더 없이 동작하는 두 가지 핵심 메커니즘은?
기대 키워드
자주 하는 오해
"React Hook Form 은 useState 를 안 쓴다" 까지는 맞지만, 정확한 이유는 (1) 입력값을 ref(=DOM)에 저장하고 (2) formState 를 Proxy 로 추적해 구독한 부분만 리렌더하기 때문입니다.
Zod 스키마를 클라이언트만이 아니라 서버에서도 검증해야 하는 이유는?
기대 키워드
자주 하는 오해
"클라에서 막으면 안전하다" 는 가장 위험한 오해. DevTools 로 onSubmit 우회나 직접 fetch 가 가능하므로 서버는 항상 같은 스키마로 다시 검증해야 합니다.
학습 자료
- React Hook Form — Get Startedregister/handleSubmit/formState 의 기본 사용, Controller, useFieldArray 까지 공식 가이드. 모든 답의 출발점.DocReact Hook Form 공식
- Zod — Intro스키마 정의, z.infer 타입 추론, refine, safeParse 등 핵심 API. v4 에서 변경된 부분도 정리.DocZod 공식
- @hookform/resolvers — Zod/Yup/Valibot/ArkType etc.RHF 를 다양한 검증 라이브러리와 잇는 어댑터 모음. Zod 외에 Valibot/ArkType/Standard Schema 까지 지원.CodeReact Hook Form Resolvers
- Controlled and uncontrolled components — React docs (legacy)controlled/uncontrolled 의 정의와 권장 패턴. 새 react.dev 에선 분산되어 있어 legacy 페이지가 가장 명료.DocReact 공식 문서
- useActionState — ReactServer Action 응답을 form state 로 받는 훅. progressive enhancement 가 가능한 native form 패턴의 핵심.DocReact 19 공식