제어 컴포넌트와 비제어 컴포넌트의 개념 및 차이, 어떤 상황에서 사용해야 하는지에 대해 간략하게 정리해 보자.
제어 컴포넌트
아래의 코드는 input 태그의 value가 바뀔 때마다 onChange 함수를 통해 state가 업데이트되는 로직이다.
export default function App() {
const [name, setName] = useState("");
const onChange = (e) => {
setName(e.target.value);
};
return (
<div className="App">
<input onChange={onChange} />
</div>
);
}
제어 컴포넌트 방식은 사용자의 입력을 기반으로 state를 관리하는 방식을 말한다. setState 함수를 사용하여 값을 관리하는 방식(React에 의해 값이 제어되는 방식)을 제어 컴포넌트 방식이라고 생각하면 편하다. 제어 컴포넌트는 사용자가 입력한 값과 저장되는 값이 실시간으로 동기화되는데, 이를 "single source of truth"라고 한다.
제어 컴포넌트는 입력할 때마다 렌더링 되기 때문에 불필요한 렌더링이나 API 호출이 발생할 수 있다. 나도 입력 폼을 만들 때 제어 컴포넌트 방식을 주로 사용하였는데, onChange 함수 내에 console.log(e.target.value)를 추가하면 한 글자씩 입력할 때마다 미친 듯이 log가 늘어났던 것을 생각하면 바로 이해된다.
ㅇ
아
안
안ㄴ
안녀
안녕
이런 식으로 불필요한 단어 입력 시에도 값이 갱신되어 버린다. 이는 자원 낭비 문제로도 연결될 수 있다.
이를 해결하기 위해 스로틀링이나 디바운싱 (throttle&debounce)을 사용할 수 있다.
쓰로틀링: 마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않도록 하는 것
디바운싱: 연이어 호출되는 함수들 중 마지막 함수(또는 제일 처음)만 호출하도록 하는 것
https://www.zerocho.com/category/JavaScript/post/59a8e9cb15ac0000182794fa
lodash에서도 해당 기능을 제공하므로 필요할 경우 사용하는 것도 좋을 것 같다고 한다(나중에 찾아봐야지...)
하지만 일반적으로 모든 form 요소에서 상태의 동기화가 필요한 건 아니며, form 요소가 증가할수록 모든 컴포넌트에 쓰로틀링이나 디바운싱을 걸기는 힘들다. 만약 값이 트리거 된 이후에만 갱신이 돼도 문제가 없다면, ref를 사용하는 방식이 불필요한 렌더링을 방지하는데 도움이 될 수 있다. 비제어 컴포넌트를 사용해 렌더링을 최적화하는 라이브러리인 react-hook-form이라는 라이브러리도 있다고 하는데, 이것도 나중에 사용해 볼 기회가 있다면 더 찾아보는 걸로 해야겠다.
비제어 컴포넌트
비제어 컴포넌트는 기존의 바닐라 자바스크립트를 사용했을 때와 크게 다르지 않다. 바닐라 자바스크립트를 사용하여 폼을 제출할 때 (submit button)을 클릭하면 요소 내부의 값을 가져올 수 있었다. 비제어 컴포넌트 또한 이와 유사하다.
비제어 컴포넌트 방식은 제어 컴포넌트 방식에서 사용한 setState() 대신 ref를 사용해서 값을 얻는 방식을 말한다.
export default 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>
);
}
비제어 컴포넌트는 제어 컴포넌트와는 달리 값이 실시간으로 동기화되지 않는다. 만약 a와 b라는 컴포넌트가 있을 때, a에 대한 변화 발생 시 즉각적으로 b가 영향을 받아야 할 때 비제어 컴포넌트는 이런 방식에 대한 대응을 할 수 없다.
즉, 제어 컴포넌트의 경우 사용자가 입력할 때마다 리렌더링을 발생시키는 반면, 비제어 컴포넌트는 사용자로 인해 발생하는 트리거 발생 이전까지는 리렌더링을 발생시키지도 않고 값을 동기화시키지도 않는다.
참고) useRef와 리렌더링
useRef는 왜 리렌더링을 발생시키지 않는 걸까?
useRef()는 자바스크립트 엔진의 heap 영역에 저장되는 일반적인 자바스크립트 객체이다.
매번 렌더링 할 때 동일한 객체를 제공한다. heap에 저장되어 있기 때문에 애플리케이션이 종료되거나 가비지 컬렉팅 될 때까지 참조할 때마다 같은 메모리 값을 가진다고 할 수 있다.
값이 변경되어도 리렌더링이 되지 않는다. 같은 메모리 주소를 갖고 있기 때문에 자바스크립트의 === 연산이 항상 true를 반환한다. 즉 변경사항을 감지되지 않아 리렌더링을 하지 않는 것이다.
# 힙?!
힙(heap)은 동적 메모리 할당에 사용되는 자료구조이다. 이 힙을 이용하여 V8은 객체 또는 동적 데이터를 저장한다. 여기에 저장되는 메모리는 V8 엔진 내부에서 가장 큰 공간을 차지하고 있으며, 가비지 컬렉션 또한 발생하는 곳이다.
제어 컴포넌트 vs 비제어 컴포넌트
기능 | 제어 컴포넌트 | 비제어 컴포넌트 |
일회성 정보 검색 (예: 제출) | O | O |
제출 시 값 검증 | O | O |
실시간으로 필드값의 유효성 검사 | O | X |
조건부로 제출 버튼 비활성화 (disabled) | O | X |
실시간으로 입력 형식 적용하기 (숫자만 가능하게 등) | O | X |
동적 입력 | O | X |
- 실시간으로 값에 대한 피드백이 필요하다! -> 제어 컴포넌트
- 즉각적인 피드백이 불필요하고 제출 시에만 값이 필요하다! 불필요한 렌더링과 값 동기화가 싫다! -> 비제어 컴포넌트
참고한 블로그)
'온라인 강의(유데미, 인프런 등) > React 완벽 가이드(유데미)' 카테고리의 다른 글
[react & typescript] CRA로 typescript 설정하기(feat. esLint, styled-component) (0) | 2023.02.23 |
---|---|
[react] forwardRef (1) | 2023.02.22 |
[react] context API (0) | 2023.02.22 |
[react] useReducer(feat. useState와 비교) (0) | 2023.02.21 |
[react] useEffect에서 Clean-up 함수 사용하기 (0) | 2023.02.21 |