개구리 개발자
[하루 한 문항, 기술면접 준비] 1편 - 리액트의 Controlled Component와 Uncontrolled Component 본문
[하루 한 문항, 기술면접 준비] 1편 - 리액트의 Controlled Component와 Uncontrolled Component
fr0gydev 2025. 8. 19. 10:55📬 매일메일 소개
매일메일은 '개발자를 위한 기술 면접 메일링 서비스'로, 바쁜 개발자들이 매일 기술 면접 질문을 찾아보고 정리하기 어려운 상황을 대신해, 백엔드/프론트엔드 분야에 맞는 질문을 선별해 메일 형태로 전달해 주는 큐레이션 기반 서비스이다. 별도의 비용 없이 이용할 수 있는 무료 서비스이며, 실무 인터뷰에서 자주 등장하는 핵심 개념 기반 질문들이 포함되어 있기 때문에 "매일 하나씩 정리하면서 인터뷰 대비를 하고 싶은 개발자"에게 매우 유용한 서비스이다.
❓ 오늘의 질문
"리액트의 Controlled Component와 Uncontrolled Component의 차이점에 대해서 설명해주세요."
이 질문은 React에서 입력 폼을 어떤 방식으로 제어하는지를 정확하게 이해하고 있는지를 묻는 질문으로, 프론트엔드 면접에서 매우 자주 등장한다. 상태 관리 흐름을 얼마나 명확히 이해하고 있는지를 확인할 수 있기 때문에, 초급 이상이라면 반드시 알고 있어야 하는 기본 개념이다.
React 공식 문서에서는 컴포넌트 간에 state를 공유하고 싶을 때는 다음과 같이 설명하고 있다:
때때로 두 컴포넌트의 state가 항상 함께 변경되기를 원할 수 있습니다.
그렇게 하려면 각 컴포넌트에서 state를 제거하고 가장 가까운 공통 부모 컴포넌트로 옮긴 후 props로 전달해야 합니다. 이 방법을 State 끌어올리기라고 하며 React 코드를 작성할 때 가장 흔히 하는 일 중 하나입니다.
즉, 자식 컴포넌트 각각이 자신의 state를 갖고 있을 때는 서로 독립적으로 동작하기 때문에, 하나의 흐름으로 함께 제어하고 싶다면 부모 컴포넌트로 state를 옮기고, 해당 값을 props로 자식에게 내려주는 방식이 모범적인 패턴이라는 뜻이다.
React에서는 폼 입력이나 UI 상태 등을 관리할 때 Controlled Component와 Uncontrolled Component로 나누어 접근할 수 있다. Controlled Component는 React가 state를 통해 값을 직접 제어하는 방식이고, Uncontrolled Component는 DOM이 직접 값을 관리하며 필요할 때 ref로 값을 읽는 방식이다.
여기에 더해, 공식 문서에서는 여러 컴포넌트가 같은 state를 함께 업데이트해야 하는 상황에 대해서도 명확한 지침을 제시하고 있다. 공식 문서에 따르면, "각 컴포넌트의 state를 제거하고 공통 부모 컴포넌트로 state를 올린(lifting state up) 뒤, 이 값을 props로 자식 컴포넌트에 전달하는 것"이 권장되는 방식이다.
예를 들면, Accordion과 Panel 구조에서 각 Panel이 독립적으로 isActive 상태를 갖고 있을 때, Panel A의 상태를 변경해도 Panel B에는 영향이 없다. 하지만 한 번에 하나의 Panel만 열리도록 동기화하려면, 각 Panel의 state를 관리하는 주체를 부모 컴포넌트로 옮긴 뒤, 그 상태 정보를 isActive라는 props으로 내려주면 된다. 자식은 그 prop에 따라 UI를 렌더링하고, 클릭 이벤트가 발생하면 부모의 상태를 업데이트하는 방식을 사용하는 것이 보다 견고하고, 예측 가능한 구조를 만든다.
다시 정리하자면, Controlled vs Uncontrolled는 개별 컴포넌트 내부의 상태 제어 방식 차이이고, Lifting State Up은 공통적으로 동기화되어야 하는 상태를 관리하기 위한 구조적 패턴이다.
구분 | 정의 | 장점 | 단점 |
Controlled Component | React가 입력값(state)을 직접 제어하는 방식 | 유효성 검사, 조건부 렌더링, 상태 기반 UI 구현에 유리 | 입력값 변화마다 렌더링 발생 → 성능 저하 기능 |
Uncontrolled Component | DOM이 직접 값을 제어하고 React는 ref로 값을 읽는 방식 | 코드 간결, 빠른 프로토타이핑 및 성능상 유리 | 상태 추적 어려움, 유효성 검증 로직 분리 필요 |
Controlled Component는 입력 값 자체를 React state로 관리하는 방식이다. <input>의 value를 state에 바인딩하고, onChange 이벤트가 발생할 때마다 값을 업데이트하여 React가 항상 최신 값을 보유하게 된다. 이렇게 값이 상태로 존재하면 입력 값에 따라 동적으로 UI를 변경하거나, 특정 조건에서 오류 메시지를 출력하는 등의 "상태 기반 제어"가 매우 자연스럽고 직관적으로 가능하다. 단점은 state 변경 → 렌더링 → DOM 업데이트 단계가 반복되기 때문에 렌더링 빈도가 증가해 성능 비용이 발생할 수 있다는 점이다.
const [value, setValue] = useState("");
return (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
/>
);
→ 사용자가 입력할 때마다 onChange로 값이 업데이트되고, 업데이트된 state가 다시 value로 내려가는 단방향 데이터 흐름을 갖는다.
반면, Uncontrolled Component는 값이 React state에 존재하지 않고 실제 DOM이 값을 보유한다. 개발자는 useRef를 통해 DOM 노드에 직접 접근하여 값을 가져온다. 이 경우 input의 값 변경이 state 변경을 유발하지 않기 때문에 렌더링이 발생하지 않고 코드도 단순하다. 파일 업로드 input처럼 React가 값을 직접 제어하기 어려운 경우, 또는 간단하고 빠르게 구현할 때 적합한 방식이다. 다만 React state와 분리되어 있기 때문에 유효성 검사나 조건부 렌더링을 위해서는 별도의 로직이 필요하다.
const inputRef = useRef<HTMLInputElement>(null);
return (
<input
defaultValue="hello"
ref={inputRef}
/>
);
→ React는 값을 제어하지 않고 단순히 초기값만 부여한다. 이후 input의 현재 값이 필요할 때는 inputRef.current?.value 처럼 ref를 통해 DOM에 직접 접근해서 값을 읽어온다.
"네, 리액트에서는 사용자 입력값을 제어하는 방식에 따라 Controlled Component와 Uncontrolled Component로 나뉩니다.
Controlled Component는 input의 값을 React의 state로 직접 제어하는 방식이고, onChange를 통해 반드시 상태를 업데이트하게 되어 있어서 유효성 검사나 조건부 렌더링 같은 상태 기반 UI를 구현할 때 유리합니다.
반면 Uncontrolled Component는 DOM 자체에 값이 유지되고 필요할 때 ref로 값을 읽는 방식이라 렌더링 부담이 적고 구현이 간단하지만, React 흐름과 분리되기 때문에 상태 추적이 어렵다는 단점이 있습니다.
그래서 일반적인 폼이나 상태 기반 로직이 있는 경우에는 Controlled 방식을 사용하고, 파일 업로드 input처럼 React가 값을 직접 제어할 필요가 없는 경우에는 Uncontrolled 방식으로 처리하곤 합니다."
첫 질문부터 꽤 기본적인 개념임에도 "왜 이렇게 나뉘는지"까지 차분하게 생각해 보니 생각보다 많은 포인트를 정리할 수 있었다. 앞으로도 매일 한 문항씩 정리하면서 단순히 암기하는 것이 아니라, 실제 면접에서 "내 언어"로 설명할 수 있을 만큼 소화된 내용을 기록해보려고 한다.
다음 편에서는 두 번째 질문을 이어서 정리할 예정이니, 관심 있는 분들은 같이 기술 면접 루틴을 만들어가도 좋을 것 같다 👏🏻