프로젝트를 하다보면, 유저에게서 입력을 받아야 하는 상황이 다수 존재합니다.
대표적으로, 폼을 만들어 정보를 입력받는 상황이 있습니다.
이러한 폼에서는 제어 컴포넌트로 구성할지 비제어 컴포넌트로 구성할지에 대한 고민이 필요합니다.
다음의 2가지 Form 형식을 참고하여 제어 컴포넌트와 비제어 컴포넌트가 무엇인지 알아보겠습니다.
제어 컴포넌트
리액트 공식문서에서 제공하는 제어 컴포넌트의 정의
우리는 React state를 “신뢰 가능한 단일 출처 (single source of truth)“로 만들어 두 요소를 결합할 수 있습니다.
그러면 폼을 렌더링하는 React 컴포넌트는 폼에 발생하는 사용자 입력값을 제어합니다.
이러한 방식으로 React에 의해 값이 제어되는 입력 폼 엘리먼트를 “제어 컴포넌트 (controlled component)“라고 합니다.
🔎 즉, State를 생성하여 폼에서 발생하는 값들을 관리하는 방식을 의미한다.
다음 예시 코드를 보면 이해가 쉽습니다.
function App() {
const [input, setInput] = useState("");
const onChange = (e) => {
setInput(e.target.value);
};
return (
<div>
입력값
<input onChange={onChange} />
</div>
);
}
export default App;
유저에게 입력을 받는 매우 간단한 코드입니다.
값이 변경될때마다, State로 정의한 input에 값을 반영합니다.
그렇다면, 비제어 컴포넌트는 무엇일까요?
비제어 컴포넌트
마찬가지로, 리액트 공식문서에서는 비제어 컴포넌트에 대해 다음과 같이 설명합니다.
비제어 컴포넌트는 DOM 자체에서 폼 데이터가 다루어집니다.
모든 state 업데이트에 대한 이벤트 핸들러를 작성하는 대신 비제어 컴포넌트를 만들려면 ref를 사용하여 DOM에서 폼 값을 가져올 수 있습니다.
즉, 기존에 JQuery 혹은 Vanilla Javascript를 통해 DOM 객체에 직접 접근하여 값을 가져오는 방식을 의미합니다.
React에서는 useRef를 통해 DOM 객체에 직접 접근가능합니다.
다음 간단한 예시 코드를 살펴봅시다.
function App() {
const inputRef = useRef(); // ref 사용
const onClick = () => {
console.log(inputRef.current.value);
};
return (
<div className="App">
<input ref={inputRef} />
<button type="submit" onClick={onClick}>
전송
</button>
</div>
);
}
export default App;
해당 폼은 useRef Hook을 통해 사용자가 입력한 값을 폼 전송시에 접근합니다.
이렇게, 리액트에서의 제어 컴포넌트와 비제어 컴포넌트의 개념에 대해 알아보았습니다.
그렇다면 어떤 컴포넌트 형식을 언제 활용해야 할까요?
이를 알아보기 위해서, 먼저 각 방식의 장단점에 대해 분석해봅시다.
제어 컴포넌트의 장단점
장점
관리하는 값을 최신 상태 유지시킨다.
→ 유효성 검사 가능 (ex. 이메일 형식)
→ 실시간 피드백 제공 (ex. 비밀번호 복잡도)
단점
값이 바뀔때마다 리렌더링된다.
제어 컴포넌트에서의 단점은 생각보다 치명적입니다.
이전에 보았던 코드에서, 리렌더링 될때마다 콘솔로그를 찍어봅시다.
let render = 0;
function App() {
const [input, setInput] = useState("");
const onChange = (e) => {
setInput(e.target.value);
};
const printRender = () => {
console.log(render);
};
useEffect(() => {
console.log(input);
render++;
}, [input]);
return (
<div>
입력값
<input onChange={onChange} />
<button type="button" onClick={printRender}>
렌더링 횟수 출력
</button>
</div>
);
}
export default App;
결과
이메일 하나 입력하는데 무려 20번의 Rerendering이 발생했습니다.
반면, 예상할 수 있듯이 비제어 컴포넌트는 Rerendering 횟수를 최소화할 수 있습니다.
마찬가지로, 기존 코드를 변형하여 봅시다.
import { useEffect, useRef } from "react";
let render = 0;
function App() {
const inputRef = useRef(); // ref 사용
const printRender = () => {
console.log(render);
};
useEffect(() => {
render++;
}, [inputRef.current?.value]);
return (
<div className="App">
입력값
<input ref={inputRef} />
<button type="button" onClick={printRender}>
렌더링 횟수 출력
</button>
</div>
);
}
export default App;
결과
비제어 컴포넌트는 렌더링 횟수에서 제어 컴포넌트보다 우위에 있다고 이야기할 수 있습니다.
그렇다면 언제, 무엇을 사용해야 할까요?
장단점 분석의 뉘양스로는 제어 컴포넌트는 사용하지 않아야 할 것 같습니다.
하지만, 아닙니다.
무엇을 써야 할지는 상황에 따라 바뀝니다.
값을 추적하여 사용자에게 유효성 검사와 같은 기능을 제공해야 한다면 제어 컴포넌트를,
버튼을 클릭했을때 전체 유효성을 검사하는 방식을 선택해야 한다면 비제어 컴포넌트를 사용하면 됩니다.
물론, 하나의 폼에서 제어 컴포넌트와 비제어 컴포넌트를 동시에 사용하는 것도 괜찮습니다.
다만 그럴경우, 비제어 컴포넌트와 제어 컴포넌트는 컴포넌트화하여 렌더링되는 부분을 최소화하는 것이 좋겠습니다.
이상으로, 제어 컴포넌트와 비제어 컴포넌트가 무엇인지 알아보았습니다.
다음 글에서는 비제어 컴포넌트를 효율적으로 관리하는 React-Hook form 라이브러리 사용법에 대해 알아보겠습니다.
'웹 프로그래밍 > React' 카테고리의 다른 글
NPM 패키지 개발 1편 [타 라이브러리 사용 트러블 슈팅 과정] (0) | 2025.01.09 |
---|---|
[Tanstack Query] setMutationDefaults로 key 모듈화하여 관리하기 (0) | 2024.12.12 |
[Tanstack Query] queryFc의 인자 FunctionContext에 대해 알아보자 (1) | 2024.11.26 |
[Zustand] 상태관리 방법 및 Little Bit Deep Dive (0) | 2024.07.22 |
(React) JSX란 무엇인가? (0) | 2024.07.16 |