안녕하세요 메이토입니다 🍅
제어 컴포넌트와 비제어 컴포넌트라는 개념, 리액트를 공부하다보면 한번쯤 들어보셨을텐데요 과연 어떤 차이가 있는 걸까요?
제어 컴포넌트
const App = () => {
const [value, setValue] = useState("");
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};
return <input value={value} onChange={handleInputChange}></input>;
};
React에서 값이 제어되는 컴포넌트를 이야기 합니다.
State에 의해 값이 관리되고, 사용자의 입력에 따라 실시간으로 업데이트 해줄 수 있습니다. 즉, 값이 변경됨에 따라 상태가 변경되고, 결국 컴포넌트가 재렌더링되는 상황이 발생합니다.
즉, 입력값이 변경됨에 따라 onChange에 전달되어 있는 콜백함수가 실행되는데, 예시에 따르면 handleInputChange가 되겠네요!
결국, 콜백함수의 호출에 따라 form 컴포넌트에 값의 변화를 'push' 해주는 모습이 됩니다.
그렇기 때문에 제어 컴포넌트는 항상 최신 값을 가질 수 있게 되는 거고, 결국 UI와 상태가 동기화됨을 의미합니다.
그렇다면 제어 컴포넌트는 어디에 사용할 수 있을까요?
1. 실시간 유효성 검사
유효성 검사를 하고, 실시간으로 유효한지 여부에 대한 부분을 보여줘야 할 경우가 있을 것 같네요 ! Input에 입력되는 값이 변경됨에 따라 실시간으로 유효성 검사가 계속 이루어 져야 하고, 그 유효성 여부에 맞게 화면에 그려줘야 하기 때문에, 실시간 유효성 검사는 제어 컴포넌트로 구성되어야 합니다.
2. 조건에 따라 특정 UI 활성화
예를 들어, 모든 값이 유효해야만 제출 버튼이 활성화되는 경우가 있을 수 있겠네요. "모든 값이 유효해야 함"을 판단하기 위해서, 실시간으로 사용자의 입력에 따라 변화하는 입력값에 대한 유효성 검사를 해줘야 합니다. 이 경우 ref로 값을 가져오게 되면 매번 수동으로 읽어야 한다는 불편함이 있겠죠?
단일 진리의 원천(Single Source of Truth)
제어 컴포넌트라고 하면 빠지지 않는 이야기가 있습니다. React 컴포넌트들은 자체적으로 상태를 가질 수 있는데, 이 SSOT 원칙에 따르면 각각의 고유한 상태에 대해 어떤 컴포넌트가 해당 상태를 가질지 선택해야 한다는 것을 의미합니다. 두 컴포넌트가 같은 상태를 공유해야 하는 경우에 특히 중요한데, 이 때는 SSOT원칙에 따라 부모 컴포넌트로 상태를 '끌어올려' 자식 컴포넌트에 props에 전달하는 방식으로 구성해야 합니다.
비제어 컴포넌트
import { useRef } from "react";
const App = () => {
const inputRef = useRef<HTMLInputElement>(null);
const handleSubmit = () => {
if (inputRef.current) {
alert(`입력한 값은: ${inputRef.current.value}`);
}
};
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={handleSubmit}>제출</button>
</div>
);
};
export default App;
DOM 에 의해 값이 제어되는 컴포넌트를 이야기 합니다.
ref에 의해 ref.current로 값을 받아올 수 있는데, 이 방식은 결국 실제 DOM 에 접근하여 값을 꺼내오는 방식을 의미합니다.
React가 상태를 관리하지 않기 때문에 초기값만 설정하고, 이후에는 React가 관여하지 않는 입력 필드라고 이해해도 좋을 것 같아요 😊
값을 꺼내오려면, 특정 핸들러의 이벤트가 발생되는 시점에 값을 'pull' 받아 와야 하는데, 위 예시코드를 보면 button이 클릭되는 경우 handleSubmit이라는 콜백함수가 호출되고, 콜백함수 내부에서 ref.current.value로 값을 가져오고 있는 모습입니다.
그렇다면 비제어 컴포넌트는 언제 사용하는걸까요?
1. 초기값만 필요할 때
- 입력값을 내부에서 관리하고 제출 시점에만 필요한 경우
2. 매 입력마다 리렌더링 되는 것을 방지하고 싶을 때
상태값이 바뀌면 컴포넌트 전체가 리렌더링되기 때문에 이 부분을 막기 위해서 input이 많을 때 비제어 컴포넌트로 대체하기도 합니다
-> 지금까지의 제어 컴포넌트와 비제어 컴포넌트에 대한 내용을 표로 정리하면 다음과 같겠죠? 😄
| 구분 | 제어 컴포넌트 | 비제어 컴포넌트 |
| 값의 위치 | state | DOM (ref.current.value) |
| 접근 시점 | 실시간 (onChange) | 이벤트 발생 시 (예: onClick) |
| 유효성 검사 | 실시간 가능 | 제출 시 검사 적합 |
LMS에 다음과 같은 질문이 있었는데 이 질문에 대한 개인적인 생각들입니다 . . :-)
1. 제어 컴포넌트를 지향하라는 의견이 많은 이유는 무엇일까요?
비제어 컴포넌트의 경우 ref로 값을 꺼내야 하는데, 이는 기본적으로 React의 기본틀인 선언형 방식과 맞지 않아서일 것 같습니다.
공식 문서에 따르면, 선언형 UI 와 명령형 UI 를 다음과 같은 예시로 차이를 설명했습니다 :
왼쪽은 택시 기사님께 방향을 직접 알려주는 명령형 UI, 오른쪽은 목적지만 말하고 어떻게 갈지에 대해서는 택시 기사님에게 맡기는 선언형 UI를 뜻합니다.


즉, React는 '상태가 업데이트 되면 이런 UI를 그려줘' 라는 방식으로 선언형 UI를 지향하지만 비제어 컴포넌트는 이 흐름을 우회하여 명령형으로 컴포넌트를 사용하는 방식이기 때문에 리액트의 흐름에 맞게 하려면 상태를 활용한 제어 컴포넌트를 활용해야 한다는 의견이 많은게 아닐까 싶습니다
2. useImperativeHandle 이 언급되는 이유가 무엇일까요?
useImperativeHandle은 자식 노드에 대해 전체 DOM 접근을 막고 일부 커스텀 콜백에 대해서만 접근 가능하도록 허용하는 hook으로, 리액트가 기본적으로 선언형인 점을 고려했을 때 이 훅은 컴포넌트를 '명령형' 으로 다룰 수 있게 해준다고 할 수 있습니다!
또한, useImperativeHandle는 전 게시글에서 살펴보았듯 자식 컴포넌트가 특정 기능만 부모 컴포넌트에 제공하기 위해 외부에 노출시키는 훅이라고 할 수 있는데, ref로 직접 DOM 자체를 단순히 접근하는 것 보다 useImperativeHandle을 통해 더 안전하게 외부에서 사용할 수 있다는 점에서 아마 useImperativeHandle이 비제어 컴포넌트와 같이 언급되지 않을까 생각해보았습니다.
3. 비제어 컴포넌트는 사용할 일이 없는건가요?
있죠 물론.
(1) input 타입이 file 인 경우
사용자 조작으로만 접근 가능하여 state로 제어 불가능하며, ref를 이용해서 비제어 컴포넌트로 다뤄야 합니다.
브라우저 보안 정책 상 사용자가 선택한 파일 정보는 직접 제어할 수 없다고 해요. React에서 value에 대한 상태를 지정하고 수정하려고 해도 자바스크립트에서는 input의 value를 수정하거나 세팅할 수 없게 되어있다고 합니다
(참고로 오류가 뜨거나 ,경고가 뜨지 않고 무시된다고 합니다 ..)
(2) Autofocus
input 창에 입력이 완료되면 다음 칸으로 자동으로 넘어가게 하는 것을 autofocus라고 하는데, 이 때 state 보다는 ref를 통해 직접 DOM 요소에 접근하고, focus 메서드를 호출하는 것이 자연스러워요 ! focus는 화면에 보여주는 상태값이 아니기 때문에 state로 관리할 필요가 없고, 불필요한 state의 사용은 결국 불필요한 리렌더링으로 이어집니다.
(3) 입력값을 한 번에 처리하는 경우
매 입력마다 상태를 업데이트할 필요 없이 최종적으로 한 번에 값을 가져와 처리하면 되는 상황일 경우 굳이 제어 컴포넌트를 쓰지 않겠죠?
state로 관리할 경우 앞서 많이 언급하였듯, 상태가 바뀌면 컴포넌트는 리렌더링이 되고, 불필요한 state가 많아질 수록 불필요한 리렌더링 역시 늘어나게 되기 때문에 이런 상황이라면 비제어 컴포넌트를 사용하는 것이 더 적합합니다.
연관이 있을 것 같다 라고만 생각했던 질문들인데, 이번에 리액트 스터디를 통해 상태관리를 공부하게 된 덕분에 이 질문에 답을 할 수 있었던 것 같아요 :-) 이 블로그를 읽는 여러분들도 이 질문들에 대해 생각해보시고, 다른 의견이든 같은 의견이든 좋으니 댓글로 남겨주세요 🍅
'리액트 톺아보기' 카테고리의 다른 글
| Memoization, 최적화 (0) | 2025.05.22 |
|---|---|
| [Hook] useImperativeHandle (0) | 2025.05.01 |
| [React] React의 렌더링 트리🌳 (0) | 2025.04.24 |