React에서 Controlled Component와 Uncontrolled Component의 차이를 설명해주세요.
답변 포인트
입력값의 원천이 React state인지 DOM 자체인지 생각해보세요.
정답 및 해설
빠른 요약
Controlled Component는 input의 value를 React state로 관리하고 onChange로 동기화합니다. 실시간 검증, 조건부 UI, 폼 상태 제어에 유리합니다.
폼 입력을 다룰 때 React가 값을 직접 관리하면 Controlled Component, DOM 자체가 값을 관리하고 필요할 때만 읽어오면 Uncontrolled Component라고 합니다.
Controlled Component
입력값을 React state에 저장하고, value와 onChange로 상태를 동기화합니다.
function LoginForm() {
const [email, setEmail] = useState("");
return (
<input
value={email}
onChange={(event) => setEmail(event.target.value)}
/>
);
}입력값의 원천이 React state이므로 검증, 포맷팅, 조건부 UI 처리에 유리합니다.
function SignupForm() {
const [password, setPassword] = useState("");
const isValid = password.length >= 8;
return (
<>
<input
type="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
/>
<button disabled={!isValid}>가입</button>
</>
);
}Uncontrolled Component
입력값을 React state에 저장하지 않고, ref를 통해 DOM에서 직접 읽습니다.
function SearchForm() {
const inputRef = useRef<HTMLInputElement>(null);
const handleSubmit = () => {
const keyword = inputRef.current?.value ?? "";
console.log(keyword);
};
return (
<>
<input ref={inputRef} defaultValue="react" />
<button onClick={handleSubmit}>검색</button>
</>
);
}초기값은 value가 아니라 defaultValue로 지정합니다.
언제 무엇을 쓰나요?
Controlled Component가 적합한 경우:
- 입력 중 실시간 검증이 필요할 때
- 입력값에 따라 버튼 활성화, 에러 메시지, 미리보기 등이 바뀔 때
- 폼 상태를 상위 컴포넌트나 전역 상태와 동기화해야 할 때
- 입력값을 강제로 포맷팅해야 할 때
Uncontrolled Component가 적합한 경우:
- 간단한 폼에서 제출 시점에만 값이 필요할 때
- 파일 입력처럼 브라우저가 직접 관리해야 하는 값일 때
- 외부 DOM 라이브러리와 통합할 때
파일 입력은 보통 uncontrolled
function FileUploader() {
const fileRef = useRef<HTMLInputElement>(null);
const upload = () => {
const file = fileRef.current?.files?.[0];
if (!file) return;
console.log(file.name);
};
return (
<>
<input ref={fileRef} type="file" />
<button onClick={upload}>업로드</button>
</>
);
}주의사항
하나의 input에서 value와 defaultValue를 섞거나, controlled와 uncontrolled를 렌더링 중간에 바꾸면 React 경고가 발생할 수 있습니다.
// 좋지 않은 예: undefined였다가 문자열로 바뀌면 모드가 전환될 수 있음
<input value={maybeUndefined} onChange={handleChange} />
// 안전한 예
<input value={maybeUndefined ?? ""} onChange={handleChange} />정리
| 구분 | Controlled | Uncontrolled |
|---|---|---|
| 값 관리 | React state | DOM |
| 값 읽기 | state에서 즉시 접근 | ref로 필요 시 접근 |
| 장점 | 검증/동기화/조건부 UI에 강함 | 구현이 단순하고 리렌더링 부담 적음 |
| 대표 사용 | 복잡한 폼 | 파일 입력, 단순 검색 |
판단 기준과 예시
제어 컴포넌트는 폼 값을 React state가 소유하고, 비제어 컴포넌트는 DOM이 값을 소유합니다. 차이는 “값의 원천(source of truth)이 어디인가”입니다.
function ControlledInput() {
const [email, setEmail] = useState('');
return <input value={email} onChange={(e) => setEmail(e.target.value)} />;
}
function UncontrolledInput() {
const inputRef = useRef<HTMLInputElement>(null);
return <input ref={inputRef} defaultValue="" />;
}어떤 것을 선택할까?
- 실시간 유효성 검사, 입력값 포맷팅, 버튼 활성화 제어가 필요하면 제어 컴포넌트가 적합합니다.
- 파일 입력, 간단한 검색창, 외부 폼 라이브러리와의 통합에는 비제어 방식이 더 단순할 수 있습니다.
- 대형 폼은 모든 입력을 state로 올리면 렌더링 비용이 커질 수 있어 React Hook Form 같은 라이브러리가 비제어 방식을 활용합니다.
흔한 실수
// ❌ value만 있고 onChange가 없으면 읽기 전용처럼 동작
<input value={name} />
// ✅ 수정 가능하게 만들기
<input value={name} onChange={(e) => setName(e.target.value)} />value와 defaultValue를 섞으면 React 경고가 발생할 수 있습니다. 한 컴포넌트 생명주기 안에서는 제어/비제어 방식을 일관되게 유지하는 것이 좋습니다.