Skip to content

React의 제어 컴포넌트 vs 비제어 컴포넌트 ​

📘 Official Docs

제어 컴포넌트 (Controlled Component) ​

입력값을 React state가 직접 관리하는 컴포넌트를 제어 컴포넌트라고 한다.
value와 onChange를 통해 관리한다.

예시 ​

ts
function ControlledInput() {
  const [value, setValue] = useState('');

  return (
    <input
      value={value}
      onChange={(e) => setValue(e.target.value)}
    />
  );
}
  • input의 실제 값은 value state
  • DOM은 단순히 React가 내려준 값을 렌더링한다.

즉, 값의 주도권은 항상 React에 있다.

장점 ​

  • 값에 즉시 접근 가능
  • 실시간 검증 쉬움
  • React가 전체 흐름을 제어

단점 ​

  • 입력할 때마다 리렌더링 발생
  • 필드가 많아질수록 state와 핸들러 증가
  • 폼이 커질수록 코드 복잡도 증가

단, 실시간 검증이나 조건부 UI가 핵심인 경우에는
이러한 비용을 감수하고서라도 제어 컴포넌트를 사용하는 것이 자연스럽다.


비제어 컴포넌트 (Uncontrolled Component) ​

입력값을 DOM이 직접 관리하고, React는 ref로 필요할 때만 값을 가져오는 방식을 비제어 컴포넌트라고 한다.

예시 ​

tsx
function UncontrolledInput() {
  const inputRef = useRef<HTMLInputElement>(null);

  const handleSubmit = () => {
    console.log(inputRef.current?.value);
  };

  return (
    <>
      <input ref={inputRef} />
      <button onClick={handleSubmit}>확인</button>
    </>
  );
}
  • input 값은 DOM이 관리
  • React는 필요할 때만 값을 조회

즉, 값의 주도권은 DOM에 있다.

장점 ​

  • 입력 중 리렌더링 없음
  • 불필요한 state 제거 가능
  • 대규모 폼에서 성능상 유리

단점 ​

  • 값의 흐름이 눈에 보이지 않음
  • 실시간 검증, 조건부 UI에 불리
  • 단순 구현은 쉽지만, 검증, 에러 처리, 제출 흐름이 분산되기 쉬워 구조화가 어렵다.

React Hook Form에서의 제어 / 비제어 ​

React Hook Form은 비제어 컴포넌트 방식을 기반으로 설계되었다. 하지만 단순히 ref만 쓰는 라이브러리는 아니다.

  • 비제어 컴포넌트 + 폼 상태 관리 + 유효성 검증 + 제출 흐름 제어
  • 즉, DOM이 입력값을 관리하고 React Hook Form이 그 값을 수집·검증·관리한다.

register ​

tsx
<input {...register('email')} />

register는 내부적으로 ref, onChange, onBlur, name를 input에 주입한다.
(DOM 참조, 값 변경 감지, 포커스 해제 감지, 필드 이름)
DOM은 그대로 값을 관리하지만, React Hook Form은 ref와 이벤트를 통해 값의 변경 사실만 추적한다.


왜 controlled + register를 섞으면 안 될까? ​

tsx
// 잘못된 예
<input {...register('email')} value={email} />

위 코드는 DOM에게도 값을 맡기고, React state에게도 값을 맡기는 구조다.
즉, 값의 주도권이 두 군데로 갈라진다.
이 상태에서는 React와 DOM 중 누가 실제 값을 책임지는지 알 수 없게 된다. 그래서 RHF는 이 사용을 금지한다.


그럼 제어 컴포넌트가 필요한 경우는? ​

  • 외부 UI 라이브러리 (MUI, Ant Design)
  • 내부적으로 value/onChange를 강제하는 컴포넌트

이때 사용하는 것이 Controller다.

tsx
<Controller
  name='email'
  control={control}
  render={({ field }) => <CustomInput {...field} />}
/>

Controller는 내부적으로 제어 컴포넌트를 사용하면서 React Hook Form과 안전하게 연결해준다.

즉, Controller는 제어 컴포넌트를 쓰지 않을 수 없는 상황에서
React Hook Form의 규칙을 깨지 않도록 도와주는 어댑터다. (내부적으로는 RHF가 제어 컴포넌트를 비제어 방식처럼 다룰 수 있게 감싸준다)



정리

  • 제어 컴포넌트: React state가 값을 관리 → 실시간 제어 가능, 리렌더링 발생
  • 비제어 컴포넌트: DOM이 값을 관리 → 성능 우수, 실시간 제어 어려움
  • React Hook Form: 비제어 컴포넌트를 기반으로 폼 상태와 검증 흐름을 관리
  • Controller: 제어 컴포넌트와 React Hook Form을 연결하기 위한 공식 API

마지막 업데이트 날짜: